Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ own, ad delivery, and the bank feed behind it. Published from
[/docs/cli](https://crawlproof.com/docs/cli).

```sh
npm install -g @profullstack/crawlproof
curl -fsSL https://crawlproof.com/install.sh | sh # or: npm install -g @profullstack/crawlproof
echo '{ "token": "crp_…" }' > ~/.crawlproof.json && chmod 600 ~/.crawlproof.json

crawlproof dashboard # five live screens: ROI, Traffic, Ads, Money, Spend
Expand Down
5 changes: 4 additions & 1 deletion app/(marketing)/docs/cli/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export default function CliDocsPage() {

<section className="mt-10 space-y-3">
<h2 className="text-2xl font-bold">Install</h2>
<Cmd note="Needs Node 22.6 or newer for the dashboard. stats and --json run anywhere.">
<Cmd note="Uses bun if present, otherwise npm with Node 22.6 or newer. Never asks for sudo.">
curl -fsSL https://crawlproof.com/install.sh | sh
</Cmd>
<Cmd note="Or with npm. Needs Node 22.6 or newer for the dashboard. stats and --json run anywhere.">
npm install -g @profullstack/crawlproof
</Cmd>
<p className="text-sm leading-relaxed">
Expand Down
5 changes: 5 additions & 0 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const body = `# CrawlProof
- AI-bot rules for GPTBot, ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot, Applebot-Extended, CCBot
- Positioning clarity: who, what, audience, pricing, CTA

## CLI
- Install: curl -fsSL ${env.siteUrl}/install.sh | sh (or npm install -g @profullstack/crawlproof; the unscoped "crawlproof" package is not ours)
- crawlproof dashboard, crawlproof stats <site>, crawlproof ads; authenticates with an API token (CRAWLPROOF_TOKEN or ~/.crawlproof.json)
- Docs: ${env.siteUrl}/docs/cli

## Contact
- General: hello@crawlproof.com
- Sales: sales@crawlproof.com
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
What the fleet costs and what it returns, in a terminal.

```
npm install -g @profullstack/crawlproof
curl -fsSL https://crawlproof.com/install.sh | sh # or: npm install -g @profullstack/crawlproof
crawlproof dashboard
```

Expand Down
2 changes: 1 addition & 1 deletion proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,6 @@ export async function proxy(request: NextRequest) {

export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|llms.txt|skill.md|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|llms.txt|skill.md|install.sh|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
56 changes: 56 additions & 0 deletions public/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/sh
# Install the crawlproof CLI (npm: @profullstack/crawlproof): traffic, ads and
# money for every site you own, in a terminal.
#
# curl -fsSL https://crawlproof.com/install.sh | sh
#
# Uses bun if present, otherwise npm (Node >= 22.6). When the global npm
# prefix is not writable it installs into ~/.local instead of asking for sudo.
# Re-running it upgrades to the latest release.
#
# The package is scoped: the unscoped `crawlproof` on npm is someone else's.
set -eu

PKG="@profullstack/crawlproof"
BIN="crawlproof"

say() { printf '%s\n' "$*"; }
die() { printf 'crawlproof install: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

say "Installing crawlproof (CrawlProof CLI)..."

if have bun; then
bun add -g "$PKG@latest"
BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin"
elif have npm && have node; then
node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=6)?0:1)' 2>/dev/null \
|| die "Node >= 22.6 required (found $(node -v)). Upgrade Node or install bun: https://bun.sh"
prefix=$(npm config get prefix)
modules="$prefix/lib/node_modules"
[ -d "$modules" ] || modules="$prefix/lib"
if [ -w "$modules" ] && [ -w "$prefix/bin" ]; then
npm install -g "$PKG@latest"
BIN_DIR="$prefix/bin"
else
BIN_DIR="$HOME/.local/bin"
say "npm prefix $prefix is not writable; installing into $HOME/.local"
npm install -g --prefix "$HOME/.local" "$PKG@latest"
fi
else
die "needs bun (https://bun.sh) or Node >= 22.6 with npm (https://nodejs.org)"
fi

say ""
if have "$BIN"; then
say "crawlproof $("$BIN" --version 2>/dev/null || echo '') installed."
else
say "crawlproof installed to $BIN_DIR, which is not on your PATH. Add it:"
say " export PATH=\"$BIN_DIR:\$PATH\""
fi
say ""
say "Next: create an API token at https://crawlproof.com/dashboard (Social -> API tokens), then"
say " export CRAWLPROOF_TOKEN=crp_... # or put { \"token\": \"crp_...\" } in ~/.crawlproof.json"
say " crawlproof dashboard # ROI, Traffic, Ads, Money, Spend"
say " crawlproof stats example.com # one site as text"
say " crawlproof help # every command"
28 changes: 28 additions & 0 deletions tests/install-sh.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";

// `curl -fsSL https://crawlproof.com/install.sh | sh` is the CLI's one-line
// install. It is a static file in public/, and the proxy must not run on it:
// the crawl gateway would answer curl's 402 or HTML instead of the script.
const root = join(__dirname, "..");

describe("/install.sh", () => {
const script = readFileSync(join(root, "public/install.sh"), "utf8");

it("is a POSIX shell script that installs the scoped package", () => {
expect(script.startsWith("#!/bin/sh\n")).toBe(true);
expect(script).toContain('PKG="@profullstack/crawlproof"');
// The unscoped `crawlproof` on npm is not ours.
expect(script).not.toMatch(/(?:npm install -g|bun add -g) "?crawlproof\b/);
});

it("is excluded from the proxy matcher", () => {
const source = readFileSync(join(root, "proxy.ts"), "utf8");
const literal = /matcher:\s*\[\s*"([^"]+)"/.exec(source)?.[1];
expect(literal).toBeDefined();
const matcher = new RegExp(`^${literal!.replace(/\\\\/g, "\\")}$`);
expect(matcher.test("/install.sh")).toBe(false);
expect(matcher.test("/docs/cli")).toBe(true);
});
});