diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..4096273
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+# Optional local-only export targets.
+# Leave unset in public CI and ordinary GitHub Pages builds.
+STACKSCOUT_PRIVATE_EXPORT_DIR=
+STACKSCOUT_PRIVATE_EXPORT_FILE=
+STACKSCOUT_PRIVATE_STATUS_DIR=
diff --git a/.gitignore b/.gitignore
index 4bfa471..2a5cba6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@ planning-local/
.local-notes/
# Local screenshots and scratch files
+.serve.log
+.serve.err.log
tmp-*.png
tmp-*.jpg
tmp-*.jpeg
diff --git a/README.md b/README.md
index 4a807b4..37303ba 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,12 @@
`stackscout` builds **Stack Scout**, the public-facing tools destination for curated builder tools, services, APIs, MCPs, and CLIs.
-This repo remains the GitHub Pages implementation base, but the visible product is no longer a simple internal "Tools Hub" brochure. The private operational console stays separate in `W:\Repos\_local\surfaces\tools-hub-local`.
+This repo remains the GitHub Pages implementation base, but the visible product is no longer a simple internal "Tools Hub" brochure. The private operational console stays outside this public repo.
## Public vs private
- This repo is public-facing only.
-- The local launcher, manager inbox, review evidence, session state, and leak-check operations belong in `tools-hub-local`.
+- Local launchers, operations state, and review artefacts belong in the private local surface, not this repo.
- Public content must stay safe for GitHub Pages and public browsing.
- Do not rely on `.gitignore` alone to protect private data. Public output is generated from an allowlisted shared source layer.
@@ -23,7 +23,7 @@ These source files drive:
- public manifests in `data/`
- generated static pages across the public site
-- a private preview export written to `W:\Repos\_local\surfaces\tools-hub-local\data\stackscout-publishing.json`
+- an optional private preview export when `STACKSCOUT_PRIVATE_EXPORT_DIR` or `STACKSCOUT_PRIVATE_EXPORT_FILE` is set locally
## Build
@@ -68,9 +68,9 @@ Before a public refresh, bump `CACHE_NAME` in `service-worker.js` when generated
npm run refresh:site
```
-This runs the site build, runs checks, and writes private refresh status to `W:\Repos\_local\surfaces\tools-hub-local\data\stackscout-refresh-status.json`.
+This runs the site build, runs checks, and optionally writes private refresh status when `STACKSCOUT_PRIVATE_STATUS_DIR` is set locally.
-For unattended Windows refreshes without visible terminal focus theft, use the local-only launcher at `W:\Repos\_My Tools\LOCAL-ONLY\stackscout-refresh\run-stackscout-refresh.cmd`.
+For unattended Windows refreshes without visible terminal focus theft, use a local-only launcher outside this public repo and set the optional private export/status environment variables there.
## Site structure
diff --git a/catalog/index.html b/catalog/index.html
index 5c4a8e6..6afa345 100644
--- a/catalog/index.html
+++ b/catalog/index.html
@@ -676,7 +676,7 @@
SyncPad
diff --git a/categories/apis/index.html b/categories/apis/index.html
index 145d11d..4175a4c 100644
--- a/categories/apis/index.html
+++ b/categories/apis/index.html
@@ -175,7 +175,7 @@ Anthropic API
diff --git a/categories/clis/index.html b/categories/clis/index.html
index c1f4070..81b6211 100644
--- a/categories/clis/index.html
+++ b/categories/clis/index.html
@@ -175,7 +175,7 @@ Wrangler
diff --git a/categories/desktop-apps/index.html b/categories/desktop-apps/index.html
index 5391e8e..c98a026 100644
--- a/categories/desktop-apps/index.html
+++ b/categories/desktop-apps/index.html
@@ -141,7 +141,7 @@ SyncPad
diff --git a/categories/index.html b/categories/index.html
index 70bbb47..c9acbbd 100644
--- a/categories/index.html
+++ b/categories/index.html
@@ -228,7 +228,7 @@ Desktop Apps
diff --git a/categories/mcps/index.html b/categories/mcps/index.html
index 8a410de..f862d74 100644
--- a/categories/mcps/index.html
+++ b/categories/mcps/index.html
@@ -175,7 +175,7 @@ Playwright MCP
diff --git a/categories/web-apps/index.html b/categories/web-apps/index.html
index a756d06..cb3bc04 100644
--- a/categories/web-apps/index.html
+++ b/categories/web-apps/index.html
@@ -209,7 +209,7 @@ Strudel Studio
diff --git a/categories/web-services/index.html b/categories/web-services/index.html
index 56ff97d..cf70f1a 100644
--- a/categories/web-services/index.html
+++ b/categories/web-services/index.html
@@ -175,7 +175,7 @@ Make
diff --git a/categories/websites/index.html b/categories/websites/index.html
index 2107b21..999627d 100644
--- a/categories/websites/index.html
+++ b/categories/websites/index.html
@@ -175,7 +175,7 @@ Hugging Face
diff --git a/collections/index.html b/collections/index.html
index c4ad6eb..ae64571 100644
--- a/collections/index.html
+++ b/collections/index.html
@@ -175,7 +175,7 @@ From the Stack Scout Lab
diff --git a/data/categories-manifest.json b/data/categories-manifest.json
index dc7e493..78826b4 100644
--- a/data/categories-manifest.json
+++ b/data/categories-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Categories",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"categories": [
{
"slug": "websites",
diff --git a/data/collections-manifest.json b/data/collections-manifest.json
index 0e6d713..c6f8013 100644
--- a/data/collections-manifest.json
+++ b/data/collections-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Collections",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"collections": [
{
"slug": "creator-stack",
diff --git a/data/methodology-manifest.json b/data/methodology-manifest.json
index 5cf7354..f57652d 100644
--- a/data/methodology-manifest.json
+++ b/data/methodology-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Methodology",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"thesis": "Stack Scout is a public guide, not a benchmark lab. We badge selectively, update from official sources where possible, and avoid pretending every fast-moving tool deserves a fixed numeric score.",
"principles": [
{
diff --git a/data/page-registry.json b/data/page-registry.json
index e4ba728..33a29ae 100644
--- a/data/page-registry.json
+++ b/data/page-registry.json
@@ -1,6 +1,6 @@
{
"title": "Stack Scout // Page Registry",
- "generatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
"pages": [
{
"key": "home",
diff --git a/data/radar-manifest.json b/data/radar-manifest.json
index 2c299ca..7d7a77b 100644
--- a/data/radar-manifest.json
+++ b/data/radar-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Radar",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"items": [
{
"title": "Azure MCP Server",
diff --git a/data/tools-manifest.json b/data/tools-manifest.json
index 28fa7b6..dee2917 100644
--- a/data/tools-manifest.json
+++ b/data/tools-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Tools Manifest",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"summary": "Public-safe catalog for Stack Scout.",
"counts": {
"total": 14,
diff --git a/data/updates-manifest.json b/data/updates-manifest.json
index 4ae33c6..779cf1b 100644
--- a/data/updates-manifest.json
+++ b/data/updates-manifest.json
@@ -1,7 +1,7 @@
{
"title": "Stack Scout // Updates",
- "generatedAt": "2026-05-25",
- "updatedAt": "2026-05-25",
+ "generatedAt": "2026-06-04",
+ "updatedAt": "2026-06-04",
"summary": "Public-safe activity stream seeded from official or first-party sources.",
"items": [
{
diff --git a/index.html b/index.html
index ccc9d71..ec7781b 100644
--- a/index.html
+++ b/index.html
@@ -833,7 +833,7 @@ From the Stack Scout Lab
diff --git a/method/index.html b/method/index.html
index 8b44939..aa83e0d 100644
--- a/method/index.html
+++ b/method/index.html
@@ -201,7 +201,7 @@ Verdict language
diff --git a/radar/index.html b/radar/index.html
index aad5067..4bc907f 100644
--- a/radar/index.html
+++ b/radar/index.html
@@ -164,7 +164,7 @@ GitHub skills ecosystem
diff --git a/scripts/build-stackscout.js b/scripts/build-stackscout.js
index 250398c..d390a74 100644
--- a/scripts/build-stackscout.js
+++ b/scripts/build-stackscout.js
@@ -4,10 +4,6 @@ const path = require('path')
const ROOT_DIR = path.resolve(__dirname, '..')
const SOURCE_DIR = path.join(ROOT_DIR, 'content', 'stackscout')
const DATA_DIR = path.join(ROOT_DIR, 'data')
-const PRIVATE_PREVIEW_EXPORT_CANDIDATES = [
- 'W:\\Repos\\_local\\surfaces\\tools-hub-local\\data\\stackscout-publishing.json',
- '\\\\nas_storage_1\\Workspaces\\Repos\\_local\\surfaces\\tools-hub-local\\data\\stackscout-publishing.json',
-]
const BUILD_NOW = new Date()
const GENERATED_AT = BUILD_NOW.toISOString().slice(0, 10)
const GENERATED_AT_ISO = BUILD_NOW.toISOString()
@@ -54,17 +50,16 @@ function writeExternalJson(absolutePath, value) {
fs.writeFileSync(absolutePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
}
-function resolveWritableExternalPath(candidates) {
- for (const candidate of candidates) {
- try {
- ensureParent(candidate)
- return candidate
- } catch (error) {
- continue
- }
+function resolvePrivatePreviewExportPath() {
+ if (process.env.STACKSCOUT_PRIVATE_EXPORT_FILE) {
+ return path.resolve(process.env.STACKSCOUT_PRIVATE_EXPORT_FILE)
}
- throw new Error(`Unable to resolve writable external path from candidates: ${candidates.join(', ')}`)
+ if (process.env.STACKSCOUT_PRIVATE_EXPORT_DIR) {
+ return path.join(path.resolve(process.env.STACKSCOUT_PRIVATE_EXPORT_DIR), 'stackscout-publishing.json')
+ }
+
+ return null
}
function escapeHtml(value) {
@@ -1279,8 +1274,7 @@ ${routes.map((route) => ` ${PUBLIC_BASE_URL}${route}`).jo
}
function main() {
- const privatePreviewExport =
- process.platform === 'win32' ? resolveWritableExternalPath(PRIVATE_PREVIEW_EXPORT_CANDIDATES) : null
+ const privatePreviewExport = resolvePrivatePreviewExportPath()
const site = readJson('site-source.json')
const tools = readJson('tools-source.json')
const updates = readJson('updates-source.json').sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
diff --git a/scripts/refresh-stackscout.ps1 b/scripts/refresh-stackscout.ps1
index fe3be49..cc072d6 100644
--- a/scripts/refresh-stackscout.ps1
+++ b/scripts/refresh-stackscout.ps1
@@ -6,29 +6,24 @@ param(
$ErrorActionPreference = 'Stop'
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
-$privateDataDirCandidates = @(
- 'W:\Repos\_local\surfaces\tools-hub-local\data',
- '\\nas_storage_1\Workspaces\Repos\_local\surfaces\tools-hub-local\data'
-)
-$privateDataDir = $null
-foreach ($candidate in $privateDataDirCandidates) {
- try {
- if (-not (Test-Path $candidate)) {
- New-Item -ItemType Directory -Path $candidate -Force | Out-Null
- }
+$privateDataDirInput = if ($env:STACKSCOUT_PRIVATE_STATUS_DIR) {
+ $env:STACKSCOUT_PRIVATE_STATUS_DIR
+} elseif ($env:STACKSCOUT_PRIVATE_EXPORT_DIR) {
+ $env:STACKSCOUT_PRIVATE_EXPORT_DIR
+} else {
+ $null
+}
- $privateDataDir = (Resolve-Path $candidate).Path
- break
- } catch {
- continue
+$privateDataDir = $null
+if ($privateDataDirInput) {
+ if (-not (Test-Path $privateDataDirInput)) {
+ New-Item -ItemType Directory -Path $privateDataDirInput -Force | Out-Null
}
-}
-if (-not $privateDataDir) {
- throw 'Unable to resolve a writable private data directory for Stack Scout refresh status.'
+ $privateDataDir = (Resolve-Path $privateDataDirInput).Path
}
-$statusFile = Join-Path $privateDataDir 'stackscout-refresh-status.json'
+$statusFile = if ($privateDataDir) { Join-Path $privateDataDir 'stackscout-refresh-status.json' } else { $null }
$toolsManifestFile = Join-Path $repoRoot 'data\tools-manifest.json'
$updatesManifestFile = Join-Path $repoRoot 'data\updates-manifest.json'
$categoriesManifestFile = Join-Path $repoRoot 'data\categories-manifest.json'
@@ -38,10 +33,13 @@ $durationStopwatch = [System.Diagnostics.Stopwatch]::StartNew()
function Read-JsonFile {
param(
- [Parameter(Mandatory = $true)]
[string]$Path
)
+ if (-not $Path) {
+ return $null
+ }
+
if (-not (Test-Path $Path)) {
return $null
}
@@ -82,6 +80,10 @@ function Write-RefreshStatus {
$payload[$entry.Key] = $entry.Value
}
+ if (-not $statusFile) {
+ return
+ }
+
$directory = Split-Path -Parent $statusFile
if (-not (Test-Path $directory)) {
New-Item -ItemType Directory -Path $directory -Force | Out-Null
diff --git a/service-worker.js b/service-worker.js
index bcf97f5..9ff0fb2 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -1,4 +1,4 @@
-const CACHE_NAME = 'stackscout-2026-05-25'
+const CACHE_NAME = 'stackscout-2026-06-04'
const APP_SHELL = [
'',
'index.html',
diff --git a/tools/anthropic-api/index.html b/tools/anthropic-api/index.html
index 00f4859..9274b4a 100644
--- a/tools/anthropic-api/index.html
+++ b/tools/anthropic-api/index.html
@@ -311,7 +311,7 @@ n8n
diff --git a/tools/canvas-planner/index.html b/tools/canvas-planner/index.html
index 30981ec..a333ebe 100644
--- a/tools/canvas-planner/index.html
+++ b/tools/canvas-planner/index.html
@@ -277,7 +277,7 @@ SyncPad
diff --git a/tools/gh-cli/index.html b/tools/gh-cli/index.html
index 4ce2c98..3206296 100644
--- a/tools/gh-cli/index.html
+++ b/tools/gh-cli/index.html
@@ -277,7 +277,7 @@ Wrangler
diff --git a/tools/github-mcp-server/index.html b/tools/github-mcp-server/index.html
index 9729a00..cedcfb4 100644
--- a/tools/github-mcp-server/index.html
+++ b/tools/github-mcp-server/index.html
@@ -311,7 +311,7 @@ Wrangler
diff --git a/tools/hugging-face/index.html b/tools/hugging-face/index.html
index 5cec48b..8e6dee2 100644
--- a/tools/hugging-face/index.html
+++ b/tools/hugging-face/index.html
@@ -311,7 +311,7 @@ GitHub MCP Server
diff --git a/tools/make/index.html b/tools/make/index.html
index b6ee64d..cb9981e 100644
--- a/tools/make/index.html
+++ b/tools/make/index.html
@@ -311,7 +311,7 @@ Anthropic API
diff --git a/tools/n8n/index.html b/tools/n8n/index.html
index 6bf8067..2b209f0 100644
--- a/tools/n8n/index.html
+++ b/tools/n8n/index.html
@@ -311,7 +311,7 @@ Anthropic API
diff --git a/tools/openai-api/index.html b/tools/openai-api/index.html
index e4b7c25..b5b70ae 100644
--- a/tools/openai-api/index.html
+++ b/tools/openai-api/index.html
@@ -345,7 +345,7 @@ Playwright MCP
diff --git a/tools/perplexity/index.html b/tools/perplexity/index.html
index 56142c2..226bd85 100644
--- a/tools/perplexity/index.html
+++ b/tools/perplexity/index.html
@@ -277,7 +277,7 @@ OpenAI API
diff --git a/tools/playwright-mcp/index.html b/tools/playwright-mcp/index.html
index 525d96f..d2b6b07 100644
--- a/tools/playwright-mcp/index.html
+++ b/tools/playwright-mcp/index.html
@@ -311,7 +311,7 @@ Anthropic API
diff --git a/tools/signal-stack/index.html b/tools/signal-stack/index.html
index 890b1a4..df9fb37 100644
--- a/tools/signal-stack/index.html
+++ b/tools/signal-stack/index.html
@@ -255,7 +255,7 @@ Strudel Studio
diff --git a/tools/strudel-studio/index.html b/tools/strudel-studio/index.html
index e3f09bb..dbd10d1 100644
--- a/tools/strudel-studio/index.html
+++ b/tools/strudel-studio/index.html
@@ -277,7 +277,7 @@ OpenAI API
diff --git a/tools/syncpad/index.html b/tools/syncpad/index.html
index 8506e6e..a5b9964 100644
--- a/tools/syncpad/index.html
+++ b/tools/syncpad/index.html
@@ -221,7 +221,7 @@ Canvas Planner
diff --git a/tools/wrangler/index.html b/tools/wrangler/index.html
index 6f22718..2bf1dc5 100644
--- a/tools/wrangler/index.html
+++ b/tools/wrangler/index.html
@@ -277,7 +277,7 @@ Playwright MCP
diff --git a/updates/index.html b/updates/index.html
index fdc4b3e..a824e2d 100644
--- a/updates/index.html
+++ b/updates/index.html
@@ -364,7 +364,7 @@ Wrangler 4.58.0 improved command grouping and auth ergonomics