Skip to content

feat(webscraper): connect Apify with an API token on self-hosted backends - #217

Merged
CarmenDou merged 3 commits into
mainfrom
feat/webscraper-self-hosted-connect
Jul 31, 2026
Merged

feat(webscraper): connect Apify with an API token on self-hosted backends#217
CarmenDou merged 3 commits into
mainfrom
feat/webscraper-self-hosted-connect

Conversation

@CarmenDou

@CarmenDou CarmenDou commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Adds insforge webscraper apify connect --token <token>, so an Apify account can be connected on a self-hosted backend without the OAuth flow.

Pairs with InsForge/InsForge#1824, which adds self-hosting support for the Web Scraper and ships the PUT /api/webscraper/apify/config endpoint this calls. That PR should land first.

Why

connect today only does OAuth, which is cloud-only. Agents frequently have no dashboard access — CLI is their whole surface — so without a token path a self-hosted agent cannot connect Apify on its own.

Once connected, nothing else changes: src/lib/api/apify-token.ts already fetches from the OSS host's /api/webscraper/apify/token, so scraping, the auth bridge and the apify-client path all work as-is.

Changes

  • src/lib/api/apify-config.ts (new) — storeApifyToken() PUTs to /api/webscraper/apify/config. It does not check res.ok: ossFetch throws a CLIError carrying the backend's message on any non-2xx, so a rejected token and a cloud project ("The Apify connection is managed by InsForge Cloud.") both propagate with a useful message. Same shape as the neighbouring apify-token.ts.
  • src/commands/webscraper/apify/connect.ts--token short-circuits after the project-linked check and before the login-token check, since the token path needs a linked project but not a login JWT. Without --token the OAuth flow runs completely unchanged.
  • src/lib/api/oss.ts — a route-level 404 under /api/webscraper used to be rewritten to "The Apify web scraper is cloud-only. Self-hosted: this feature is not supported." That is wrong once #1824 ships. It now says the backend predates the feature and points at --token.

Testing

npm test → 671 passing. New: apify-config.test.ts (4), connect.test.ts (9 — pins both the --token path and that the OAuth path is unchanged), plus one case for the new 404 message. Fail-before/pass-after confirmed for each.

Note

The backend endpoint is admin-authenticated through the existing ossFetch ik_ key, validates the token against Apify before storing it, and returns only a masked value — the raw token is never printed or logged by the CLI.

🤖 Generated with Claude Code


Summary by cubic

Adds a self-hosted connection path for the web scraper: insforge webscraper apify connect --token <token> stores an Apify API token via the backend instead of OAuth. Also updates the /api/webscraper 404 message to guide upgrades and the new token flow.

  • New Features

    • Add --token to insforge webscraper apify connect to connect Apify on self-hosted backends without OAuth.
    • Uses PUT /api/webscraper/apify/config; prints a masked key in text mode; JSON includes token status; no login token required; the OAuth path is unchanged.
    • Introduces storeApifyToken() and ApifyTokenStatus in src/lib/api/webscraper.ts; moves Apify connection APIs into the webscraper domain module.
  • Bug Fixes

    • /api/webscraper 404 now suggests upgrading and using --token, not that the feature is cloud-only.
    • Rejects empty or whitespace --token locally and improves errors for malformed responses or when the token is not stored.

Written for commit dd56f97. Summary will update on new commits.

Review in cubic

Note

Add --token flag to webscraper apify connect for self-hosted backends

  • Adds a --token <token> option to the insforge webscraper apify connect command, enabling API token-based connection without OAuth or browser login.
  • When --token is provided, runConnect calls the new storeApifyToken function, which sends a PUT to /api/webscraper/apify/config and returns a masked token status.
  • Non-JSON mode prints a masked key confirmation; JSON mode includes the token status in the result. The existing OAuth flow is unchanged when no token is given.
  • A 404 on /api/webscraper/* routes now surfaces an upgrade-and-token guidance message instead of a cloud-only warning.

Changes since #217 opened

  • Added validation to storeApifyToken function to reject malformed API responses and unconfigured tokens [dd56f97]
  • Added validation to runConnect function to reject empty or whitespace-only --token flag values [dd56f97]
  • Added test coverage for empty/whitespace token validation in the self-hosted apify connect command [dd56f97]
  • Added test coverage for malformed responses and unconfigured tokens in storeApifyToken function [dd56f97]

Macroscope summarized 0da73eb.

Summary by CodeRabbit

  • New Features

    • Added support for connecting self-hosted deployments to Apify with a token.
    • Added token configuration status reporting in text and JSON output.
    • Tokens are validated during setup and displayed only in masked form.
  • Bug Fixes

    • Improved guidance when the web scraper backend is unavailable, including upgrade and connection instructions.
    • Added clearer handling for invalid tokens and incomplete configuration responses.

Adds `insforge webscraper apify connect --token <token>` so agents
without dashboard access can wire up Apify on a self-hosted InsForge
backend, using the PUT /api/webscraper/apify/config endpoint instead
of OAuth. Also fixes the ossFetch 404 message under /api/webscraper,
which used to claim the feature is cloud-only and unsupported when
self-hosted; a 404 now just means the backend predates the feature.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The webscraper API now stores and validates self-hosted Apify tokens. The connect command accepts --token, reports masked status, and preserves OAuth behavior. Tests cover token storage, validation, output, OAuth paths, and backend guidance.

Changes

Apify connection workflow

Layer / File(s) Summary
Token API and backend guidance
src/lib/api/webscraper.ts, src/lib/api/webscraper.test.ts, src/lib/api/oss.ts, src/lib/api/oss.test.ts
Adds Apify token status storage through the OSS endpoint, validates returned status, and updates self-hosted configuration error guidance.
Connect command token path
src/commands/webscraper/apify/connect.ts, src/commands/webscraper/apify/connect.test.ts
Adds --token handling, masked token output, token-based connection results, and coverage for token and OAuth flows.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • InsForge/CLI#179: Introduced the Apify connect workflow and related APIs extended here.
  • InsForge/CLI#184: Modified the same Apify connect command and webscraper API paths.

Suggested reviewers: jwfing

Poem

I’m a bunny with a token to store,
Masked safely behind the CLI door.
OAuth still hops along,
Tests keep the pathway strong,
Apify connects once more!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes adding Apify API-token connection support for self-hosted webscraper backends.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/webscraper-self-hosted-connect

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CarmenDou
CarmenDou marked this pull request as ready for review July 31, 2026 01:02
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a self-hosted Apify token connection flow.

  • Adds webscraper apify connect --token <token> using the project-authenticated webscraper configuration endpoint.
  • Validates that the backend reports the token as configured before returning success.
  • Preserves the existing cloud OAuth path when --token is absent.
  • Updates route-level webscraper 404 guidance for older self-hosted backends.

Confidence Score: 5/5

The PR appears safe to merge.

The previously reported false-success path is closed because storeApifyToken now requires token.configured to be exactly true before the command reports a successful connection, and no blocking failure remains.

Important Files Changed

Filename Overview
src/lib/api/webscraper.ts Adds token storage through the OSS API and now rejects missing, malformed, or unconfigured token-status responses.
src/commands/webscraper/apify/connect.ts Adds the self-hosted token branch while retaining the existing OAuth behavior.
src/lib/api/oss.ts Replaces obsolete cloud-only webscraper 404 guidance with self-hosted upgrade instructions.
src/lib/api/webscraper.test.ts Covers successful token storage, backend errors, malformed responses, and the previously reported unconfigured-token response.
src/commands/webscraper/apify/connect.test.ts Verifies token-mode output and isolation from OAuth while preserving the original OAuth paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A["webscraper apify connect"] --> B{"--token supplied?"}
  B -->|Yes| C["Validate non-empty token"]
  C --> D["PUT /api/webscraper/apify/config via ossFetch"]
  D --> E{"configured === true?"}
  E -->|Yes| F["Return masked token status"]
  E -->|No| G["Raise CLIError"]
  B -->|No| H["Run existing OAuth flow"]
  H --> I["Run local Apify auth bridge"]
Loading

Reviews (2): Last reviewed commit: "fix: address review findings on the self..." | Re-trigger Greptile

Comment thread src/lib/api/webscraper.ts Outdated

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat(webscraper): connect Apify with an API token on self-hosted backends

Summary: A well-scoped, TDD'd addition of a --token self-hosted path to webscraper apify connect that short-circuits OAuth and stores the token server-side; clean rename of api/apify.ts → api/webscraper.ts, no dangling importers, strong tests, no blocking issues.

Requirements context

No matching spec/plan found under docs/specs/ (the only specs present are diagnose and db-migrations; there is no docs/superpowers/). Assessed against the PR description and its stated pairing with backend PR InsForge/InsForge#1824, which ships the PUT /api/webscraper/apify/config endpoint this calls.

Findings

Critical

(none)

Suggestion

  • Security (src/commands/webscraper/apify/connect.ts:47, src/lib/api/webscraper.ts:154-158) — The token is supplied as a positional flag value (--token <token>), so it lands in shell history and the process argument list (ps, /proc). The CLI itself never logs/prints the raw token (good — only the masked key is echoed, and analytics carry no secret), but the invocation surface still leaks it. Consider also accepting it from an env var or stdin (cf. docker login --password-stdin). Non-blocking.
  • Functionality (src/commands/webscraper/apify/connect.ts:98)if (opts.token) treats --token "" as absent and falls through to the OAuth path, which then fails with Not logged in. Run insforge login first. — a confusing error for what is really an empty-token input. A typeof opts.token === 'string' guard (or explicit empty-string rejection) would give a clearer message. Low blast radius.
  • Project convention (DEVELOPMENT.md §3) — Adding a user-facing flag to an existing command triggers the agent-skills sync checklist ("Did flags/defaults/output shape change? Update the skill examples."). If the insforge-cli skill documents webscraper apify connect, it's now stale re: --token. Consider opening the companion PR in InsForge/agent-skills. Cross-repo, non-blocking.

Information

  • Functionality (src/lib/api/oss.ts:212-214) — The new /api/webscraper 404 message drops the dual self-hosted/cloud guidance the sibling messages (/api/compute, /api/payments, /api/memory, /api/advisor) all keep. A genuinely outdated cloud backend that route-404s now reads "Upgrade your InsForge instance," which doesn't apply to a cloud project. In practice cloud returns a 400 ("managed by InsForge Cloud"), not a 404, so this is edge-only — but the wording is slightly less precise than its neighbours.
  • Software engineering (src/lib/api/webscraper.ts)storeApifyToken (the config PUT) now lives in webscraper.ts while the sibling fetchApifyAccessToken (the token GET) stays in apify-token.ts; the PR body also references a src/lib/api/apify-config.ts that doesn't exist in the diff (the code landed in webscraper.ts). Placement is reasonable, just worth noting the description drifted from the final layout.
  • Functionality (src/commands/webscraper/apify/connect.ts:104-108) — The token path returns a hardcoded connection: { apifyUsername: null, plan: null, status: 'active' } because the config endpoint returns only { configured, maskedKey }, not account metadata. Fine given the backend validates before storing; just an assumption to be aware of if configured can ever be false on a 2xx.

Dimension coverage

  • Software engineering — Strong. 9 connect.test.ts cases pin both the new --token path and that the OAuth path is unchanged; 4 storeApifyToken cases cover success, backend-rejection propagation, cloud-managed propagation, and malformed-response-throws; 1 oss.test.ts case guards the new 404 wording. Fail-before/pass-after claimed. Rename left no broken imports (api/apify.js has no remaining referrers). Follows repo test/analytics conventions.
  • Functionality — Implements what the PR claims. Correctly requires a linked project but not a login JWT on the token path; correctly omits opts.apiUrl from ossFetch (config lives on the OSS host, not the platform API); relies on ossFetch throwing CLIError for non-2xx so bad-token / cloud-managed messages propagate. Depends on InsForge/InsForge#1824 landing first — degrades gracefully (404 → upgrade message) if it doesn't.
  • Security — Token validated + stored server-side via the existing admin-authed ik_ key; only the masked key is surfaced; trackGroupCommand sends no secret. One non-blocking exposure noted above (argv/shell history).
  • Performance — No concerns. Single PUT, no loops, no new hot-path work.

Verdict

approved (informational — no Critical findings; the human still gives the explicit GitHub approval). Suggestions above are optional polish.

jwfing
jwfing previously approved these changes Jul 31, 2026

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/webscraper/apify/connect.ts (1)

104-108: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Hardcoded status: 'active' doesn't reflect status.configured.

The returned connection.status and connectionState are fixed literals, independent of the actual ApifyTokenStatus.configured value from the backend. If the backend can ever respond 200 with configured: false (e.g., a future "clear token" semantics on the same PUT endpoint), JSON-mode consumers of this CLI would see a misleading active/newly-connected result. Worth confirming the backend's /api/webscraper/apify/config contract guarantees configured: true on every 200 response before relying on the hardcoded value.

♻️ Suggested defensive fix
     return {
       connectionState: 'newly-connected',
-      connection: { apifyUsername: null, plan: null, status: 'active' },
+      connection: { apifyUsername: null, plan: null, status: status.configured ? 'active' : 'not-configured' },
       token: status,
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/webscraper/apify/connect.ts` around lines 104 - 108, Update the
returned object in the Apify connection flow to derive connection.status and
connectionState from the backend status.configured value instead of hardcoded
'active' and 'newly-connected' literals. Preserve the existing token assignment
and ensure configured=false produces the appropriate disconnected/unconfigured
result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/webscraper/apify/connect.ts`:
- Around line 93-98: Update the token-path condition in the command flow around
opts.token to detect when the --token option was supplied, including an empty
string, rather than relying on truthiness. Forward the supplied value to
storeApifyToken so backend validation rejects an empty token with its clear
error; only enter the OAuth flow when --token is absent.

---

Nitpick comments:
In `@src/commands/webscraper/apify/connect.ts`:
- Around line 104-108: Update the returned object in the Apify connection flow
to derive connection.status and connectionState from the backend
status.configured value instead of hardcoded 'active' and 'newly-connected'
literals. Preserve the existing token assignment and ensure configured=false
produces the appropriate disconnected/unconfigured result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c94841e4-f0ad-442e-b32d-33c5dacefb10

📥 Commits

Reviewing files that changed from the base of the PR and between 56f3d3e and 0da73eb.

📒 Files selected for processing (6)
  • src/commands/webscraper/apify/connect.test.ts
  • src/commands/webscraper/apify/connect.ts
  • src/lib/api/oss.test.ts
  • src/lib/api/oss.ts
  • src/lib/api/webscraper.test.ts
  • src/lib/api/webscraper.ts

Comment thread src/commands/webscraper/apify/connect.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/lib/api/webscraper.ts Outdated
Comment thread src/lib/api/webscraper.ts Outdated
Comment thread src/commands/webscraper/apify/connect.ts Outdated

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat(webscraper): connect Apify with an API token on self-hosted backends

Summary: A clean, well-tested addition of a --token short-circuit path to webscraper apify connect that stores an Apify token server-side via PUT /api/webscraper/apify/config, leaving the OAuth flow untouched — no blocking issues found.

Requirements context

No matching spec/plan found — this repo keeps specs under docs/specs/ (only diagnose and db-migrations designs exist), not docs/superpowers/. Assessed against the PR description, the linked backend PR (InsForge/InsForge#1824), and repo conventions in DEVELOPMENT.md.

Findings

Critical

(none) — no correctness bugs, security holes, data-loss risks, or broken existing behavior. The apify.ts → webscraper.ts rename is clean (the only importer, connect.ts, is updated; apify-token.ts is a separate file and untouched), and the OAuth path is genuinely unchanged (the --token block short-circuits before the login-token check when the option is supplied, and falls through untouched when opts.token === undefined).

Suggestion

  • Security — token supplied via argv (src/commands/webscraper/apify/connect.ts:47, 104-109): the raw token is passed on the command line, so it lands in shell history and is visible in the process list (ps aux) for the lifetime of the call. The server-side handling is exemplary (validated against Apify, only a masked value returned, never logged/printed — confirmed by connect.test.ts:135-145), so the residual exposure is purely the input channel. This is a standard CLI tradeoff and reasonable for the agent use case, but consider also accepting the token from an env var or stdin so agents/CI aren't forced to put the secret in argv. Non-blocking.
  • Convention — agent-skills doc (DEVELOPMENT.md §3): adding a user-facing --token flag is exactly the kind of command-surface change §3 says "almost certainly needs" a corresponding insforge-cli update in the agent-skills repo, otherwise agents won't know the self-hosted path exists. Out of this repo's scope, but worth a follow-up so the feature is discoverable.

Information

  • Functionality — merge ordering: the PR correctly notes it depends on InsForge/InsForge#1824 shipping the endpoint first. If this lands first, connect --token hits the route-level 404 and surfaces the new upgrade message — no crash, just an unusable flag until the backend catches up. Already called out by the author; flagging for the merge queue.
  • Functionality — token path status is assumed (connect.ts:113-117): the --token result always reports connectionState: 'newly-connected' and connection.status: 'active', even on an idempotent re-run where the token was already configured. Cosmetic; the JSON token object carries the authoritative configured/maskedKey from the backend.
  • Functionality — slightly circular 404 guidance (oss.ts:212-214): when the failing call is connect --token, the new message ("…then run insforge webscraper apify connect --token <token>") points the user back at the command they just ran. The actionable "upgrade your InsForge instance" part is correct, and the message reads well for the other caller (the bridge's /api/webscraper/apify/token fetch), so this is fine as-is.
  • SE — unchecked response cast (webscraper.ts:183): data.token is cast to ApifyTokenStatus without validating maskedKey. Impact is nil — configured is explicitly checked (!== true throws APIFY_TOKEN_NOT_STORED) and maskedKey ?? '(hidden)' (connect.ts:111) covers a missing/null mask. Noting only for completeness.

Test coverage

Excellent. connect.test.ts (9 cases) pins both the --token success/error/empty/whitespace behaviors and that the OAuth path is unchanged (fast path, slow path, still-requires-login, no token field in JSON). webscraper.test.ts (8 new storeApifyToken cases) covers masked-status success, propagated backend messages (rejected token, cloud-managed), and every malformed-2xx branch (APIFY_CONFIG_MALFORMED for non-JSON/empty/null bodies, APIFY_TOKEN_NOT_STORED for configured: false). oss.test.ts pins the new 404 wording against regression. Error codes and CLIError(message, exitCode, code) arg order match src/lib/errors.ts.

Performance

No security- or performance-relevant concerns: the token path is a single PUT, no loops, no added dependencies, nothing on a hot path.

Verdict

approved (informational — no Critical findings; the human still gives the explicit GitHub approval via the approve flow). The two Suggestions and the Information notes are non-blocking.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@CarmenDou
CarmenDou merged commit 245197d into main Jul 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants