Skip to content
Open
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
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The CLI is the right tool when you need direct shell access, scripting, or autom

- **Pipe-friendly by design**. Compact JSON when piped, human-readable output in a TTY, and explicit exit codes for authentication, validation, rate limits, and API errors.

- **Fast time to value**. From API token to your first scrape in minutes. Install with one command or use `npx` with zero setup.
- **Fast time to value**. From API key to your first scrape in minutes. Install with one command or use `npx` with zero setup.

## Use cases

Expand All @@ -69,7 +69,7 @@ Common scenarios:
## Quick start

1. **Create a free account** at [dashboard.decodo.com](https://dashboard.decodo.com/) — get up to 2K free requests with no credit card required.
2. **Get your Web Scraping API token** from the Decodo [Playground](https://dashboard.decodo.com/playground).
2. **Get your Web Data API key** from your Web Data API subscription on the Decodo [dashboard](https://dashboard.decodo.com/web-data/playground). Older plans only have a basic authentication token, which also works.
3. **Install Node.js 18+** from [nodejs.org](https://nodejs.org/) (required for npm or `npx`).
4. **Install the CLI** using one of the methods below.
5. **Authenticate and run** your first scrape:
Expand Down Expand Up @@ -111,23 +111,25 @@ npx @decodo/cli scrape https://ip.decodo.com --token "$DECODO_AUTH_TOKEN"

## Authentication

Get an auth token from the Decodo [Playground](https://dashboard.decodo.com/playground).
Use the Web Data API key from your subscription on the Decodo [dashboard](https://dashboard.decodo.com/web-data/playground).
Older plans only have a basic authentication token, which works the same way. The CLI detects which
one you provide, so the commands below take either.

```bash
# Interactive — saves token to config
# Interactive — saves the credential to config
decodo setup

# Environment variable — no saved config required
export DECODO_AUTH_TOKEN='your-token'
export DECODO_AUTH_TOKEN='your-api-key'

# Per-command override
decodo whoami --token 'your-token'
decodo whoami --token 'your-api-key'
```

**Precedence:** `--token` flag → `DECODO_AUTH_TOKEN` env var → saved config (`decodo setup`).

```bash
decodo whoami # shows token source (flag / env / config)
decodo whoami # shows credential source (flag / env / config)
decodo reset # clear saved config
```

Expand All @@ -140,7 +142,7 @@ decodo scrape https://ip.decodo.com
decodo google-search "top articles hacker news" --page-count 5 --parse
```

You should see Markdown or parsed JSON within seconds. If you see an auth error, double-check your token from the dashboard.
You should see Markdown or parsed JSON within seconds. If you see an auth error, double-check your API key on the dashboard.

## Commands

Expand All @@ -152,7 +154,7 @@ You should see Markdown or parsed JSON within seconds. If you see an auth error,
| `decodo search <query>` | Web search (`--engine google\|bing`, `--geo`, `--limit`) |
| `decodo screenshot <url>` | Capture a PNG screenshot (`-o` file or directory) |
| `decodo targets` | List all scrape targets by group |
| `decodo setup` | Save auth token interactively |
| `decodo setup` | Save your API key or auth token interactively |
| `decodo whoami` | Show configured auth source |
| `decodo reset` | Remove saved auth config |

Expand Down Expand Up @@ -252,7 +254,7 @@ Use the CLI when your agent needs to scrape from a shell, terminal, CI/CD pipeli

| Variable | Description |
| --- | --- |
| `DECODO_AUTH_TOKEN` | Auth token (overrides saved config, below `--token`) |
| `DECODO_AUTH_TOKEN` | API key or basic auth token (overrides saved config, below `--token`) |
| `DECODO_CONFIG_HOME` | Override config directory (default: `$XDG_CONFIG_HOME/decodo`, else `~/.config/decodo`) |

## Exit codes
Expand All @@ -262,15 +264,15 @@ Use the CLI when your agent needs to scrape from a shell, terminal, CI/CD pipeli
| `0` | Success |
| `1` | General error |
| `2` | Usage error (invalid flags, missing args) |
| `3` | Authentication error (missing or invalid token) |
| `3` | Authentication error (missing or invalid credential) |
| `4` | Validation error (invalid request parameters) |
| `5` | Rate limit |
| `6` | Timeout |
| `7` | API / network error |

## Troubleshooting

**`No auth token found`**
**`No API key or auth token found`**

Run `decodo setup` or export `DECODO_AUTH_TOKEN`.

Expand Down
4 changes: 2 additions & 2 deletions docs/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function Offer-Setup([string]$BinDir) {

if (-not [Console]::IsInputRedirected -and -not [Console]::IsOutputRedirected) {
Write-Host ''
Write-Host 'Next: configure your auth token.'
Write-Host 'Next: configure your API key or auth token.'
Write-Host ''
$answer = Read-Host 'Continue with setup? [Y/n]'
if ($answer -match '^[nN]') {
Expand All @@ -153,7 +153,7 @@ function Offer-Setup([string]$BinDir) {

$cmd = Get-CommandPrefix $BinDir
Write-Host ''
Write-Host "Next step: configure your auth token with $cmd setup"
Write-Host "Next step: configure your API key or auth token with $cmd setup"
Write-Host ''
}

Expand Down
4 changes: 2 additions & 2 deletions docs/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ offer_setup() {

if ! [ -t 0 ] || ! [ -t 1 ]; then
cmd=$(command_prefix "$bin_dir")
printf "\nNext step: configure your auth token with ${BOLD}%s setup${RESET}\n\n" "$cmd"
printf "\nNext step: configure your API key or auth token with ${BOLD}%s setup${RESET}\n\n" "$cmd"
return 0
fi

printf '\nNext: configure your auth token.\n\n'
printf '\nNext: configure your API key or auth token.\n\n'
printf 'Continue with setup? [Y/n] '
if ! read -r answer </dev/tty 2>/dev/null; then
cmd=$(command_prefix "$bin_dir")
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decodo/cli",
"version": "1.0.3",
"version": "1.0.4",
"description": "Official CLI for the Decodo APIs",
"license": "MIT",
"type": "module",
Expand Down
8 changes: 4 additions & 4 deletions src/auth/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { detectCredentialType } from "../services/detect-credential-type.js";
import type { DecodoConfig } from "../types/config.js";
import type { AuthCredential, AuthType } from "../types/credential.js";

const TOKEN_PROMPT = `Paste your Web Scraping API auth token (${PLAYGROUND_URL}): `;
const TOKEN_PROMPT = `Paste your Web Data API key or auth token (${PLAYGROUND_URL}): `;

interface SetupOptions {
token?: string;
Expand Down Expand Up @@ -59,8 +59,8 @@ async function verifyCredential(value: string): Promise<AuthCredential> {
}

export const setupCommand = new Command("setup")
.description("Configure the Decodo CLI with your auth token")
.option("--token <value>", "Web Scraping API auth token (non-interactive)")
.description("Configure the Decodo CLI with your API key or auth token")
.option("--token <value>", "Web Data API key or auth token (non-interactive)")
.action(async (options: SetupOptions, command) => {
const rootOpts = getRootOpts(command);
const token = (
Expand All @@ -70,7 +70,7 @@ export const setupCommand = new Command("setup")
).trim();

if (!token) {
handleCliError(new CliUsageError("auth token is required."));
handleCliError(new CliUsageError("API key or auth token is required."));
}

try {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/commands/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const CREDENTIAL_LABEL: Record<AuthType, string> = {
};

export const whoamiCommand = new Command("whoami")
.description("Show the active auth source and masked token")
.description("Show the active auth source and masked credential")
.action(async (_options, command) => {
const rootOpts = getRootOpts(command);
const { credential, source } = await resolveAuthToken({
Expand Down
5 changes: 3 additions & 2 deletions src/auth/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const PLAYGROUND_URL = "https://dashboard.decodo.com/playground";
export const PLAYGROUND_URL =
"https://dashboard.decodo.com/web-data/playground";

export const AUTH_MISSING_MESSAGE = "No auth token found.";
export const AUTH_MISSING_MESSAGE = "No API key or auth token found.";

export const AUTH_TYPE = {
TOKEN: "token",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const program = new Command()
.option("-v, --verbose", "Print debug logs to stderr")
.option(
"--token <token>",
"Auth token (overrides DECODO_AUTH_TOKEN and saved config)"
"API key or auth token (overrides DECODO_AUTH_TOKEN and saved config)"
);

async function main(): Promise<void> {
Expand Down
8 changes: 5 additions & 3 deletions src/platform/services/handle-cli-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,15 @@ export function handleCliError(

if (err instanceof AuthRequiredError) {
console.error(
"\nThe Decodo CLI is installed and working - it just needs an auth token:\n" +
` 1. Get your Web Scraping API token at ${PLAYGROUND_URL}\n` +
"\nThe Decodo CLI is installed and working - it just needs an API key or auth token:\n" +
` 1. Get your Web Data API key at ${PLAYGROUND_URL}\n` +
" 2. Run `decodo setup` to save it (or set DECODO_AUTH_TOKEN)\n" +
" 3. Re-run your command"
);
} else if (err instanceof AuthenticationError) {
console.error("Hint: Run `decodo setup` to configure your auth token.");
console.error(
"Hint: Run `decodo setup` to configure your API key or auth token."
);
}

if (err instanceof RateLimitError) {
Expand Down
10 changes: 7 additions & 3 deletions src/platform/services/prompt-hidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ function handleHiddenPromptChar(char: string, state: HiddenPromptState): void {
if (code === CHAR_EOT) {
state.cleanup();
stdout.write("\n");
state.reject(new CliUsageError("No auth token provided on stdin."));
state.reject(
new CliUsageError("No API key or auth token provided on stdin.")
);
return;
}

Expand Down Expand Up @@ -55,7 +57,9 @@ async function promptViaReadline(message: string): Promise<string> {
return await new Promise<string>((resolve, reject) => {
rl.question(message).then((answer) => resolve(answer.trim()), reject);
rl.once("close", () => {
reject(new CliUsageError("No auth token provided on stdin."));
reject(
new CliUsageError("No API key or auth token provided on stdin.")
);
});
});
} finally {
Expand Down Expand Up @@ -91,7 +95,7 @@ export async function promptHidden(message: string): Promise<string> {
const onEnd = (): void => {
state.cleanup();
stdout.write("\n");
reject(new CliUsageError("No auth token provided on stdin."));
reject(new CliUsageError("No API key or auth token provided on stdin."));
};

state.cleanup = (): void => {
Expand Down
4 changes: 2 additions & 2 deletions tests/auth/commands/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe("setupCommand", () => {
await expect(runSetup([])).rejects.toThrow("process.exit:2");

expect(exitCode).toBe(2);
expect(stderr.join("\n")).toContain("auth token is required");
expect(stderr.join("\n")).toContain("API key or auth token is required");
});

it("exits with usage when interactive prompt returns whitespace", async () => {
Expand All @@ -177,7 +177,7 @@ describe("setupCommand", () => {
await expect(runSetup([])).rejects.toThrow("process.exit:2");

expect(exitCode).toBe(2);
expect(stderr.join("\n")).toContain("auth token is required");
expect(stderr.join("\n")).toContain("API key or auth token is required");
});

it("falls back to prompt when global --token is whitespace-only", async () => {
Expand Down
4 changes: 3 additions & 1 deletion tests/platform/services/prompt-hidden.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ describe("promptHidden", () => {
listener();
}

await expect(pending).rejects.toThrow("No auth token provided on stdin.");
await expect(pending).rejects.toThrow(
"No API key or auth token provided on stdin."
);
});
});
Loading