Skip to content

feat: JSON on pCloud storage plugin (010) - #121

Merged
fatherlinux merged 4 commits into
mainfrom
feature/096-json-pcloud
Jul 31, 2026
Merged

feat: JSON on pCloud storage plugin (010)#121
fatherlinux merged 4 commits into
mainfrom
feature/096-json-pcloud

Conversation

@fatherlinux

Copy link
Copy Markdown
Member

Closes #96. Spec: specs/010-json-pcloud/.

Adds pCloud as a third storage backend, following the transport + storage-plugin pattern from JSON-on-Google-Drive. json_storage_core is untouched.

What's here

File
transports/pcloud_transport.py PCloudClient (authenticated, region-aware REST session) + PCloudTransport implementing the 5-method transport interface
json_pcloud_storage.py full storage plugin contract, wiring the transport to json_storage_core
app.py registers json-pcloud, adds /auth/pcloud + /auth/pcloud/callback, adds the provisioner to the dispatch table
static/js/storage.js, templates/index.html pCloud location/credential fields, "Connect pCloud" on the plugin card

Design decision: pCloud is storage, not identity

Google stays the identity provider — it's how the app learns the user's email and resolves their per-user backend (spec 007). The user signs in with Google, then links pCloud separately. The pCloud access token lives in the browser's IndexedDB next to the Google credentials and rides in on each request; the server never stores it.

To make that work, consumePendingAuth now merges the OAuth handoff into the stored credentials record instead of replacing it, so a Google re-login doesn't unlink pCloud (and vice versa).

_provision_json_pcloud (reached on a Google sign-in) only restates the already-linked path — the real folder creation happens once, in the pCloud callback, which is the only place a pCloud token exists server-side.

⚠️ Deployment prerequisite

The pCloud app's redirect URIs must be registered at https://docs.pcloud.com/my_apps/ before the flow completes — pCloud answers an unregistered redirect_uri with redirect_uri is not autorized. Register both:

  • https://acquacotta.crunchtools.com/auth/pcloud/callback
  • http://localhost:5000/auth/pcloud/callback

Also set PCLOUD_CLIENT_ID / PCLOUD_CLIENT_SECRET in the production env (documented in acquacotta.env.example).

Out of scope

MCP access over pCloud. The MCP server's sealed tokens carry a Drive folder id and Google credentials, and every tool resolves through json_google_drive_storage. Serving pCloud users over MCP means changing the sealed-token payload — a separate spec.

Testing

  • 269 tests pass (49 new: transport, client, plugin contract, OAuth routes, provisioner)
  • ruff check + ruff format clean
  • Container builds; smoke_test.sh passes (Flask + MCP handshake)
  • /api/plugins lists json-pcloud; /auth/pcloud 400s without an email and 302s to pCloud's consent page with one (verified against the real client id — the app renders as "Acquacotta")

🤖 Generated with Claude Code

https://claude.ai/code/session_011RYAzzVJJqrsp6TkeRS4vX

fatherlinux and others added 2 commits July 31, 2026 10:04
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds pCloud as a third storage backend, following the transport +
storage-plugin pattern established by JSON-on-Google-Drive. json_storage_core
is untouched.

- transports/pcloud_transport.py: PCloudClient (authenticated REST session,
  region-aware) + PCloudTransport implementing the 5-method transport
  interface against pCloud's path-based API.
- json_pcloud_storage.py: the full storage plugin contract, wiring the
  transport to json_storage_core.
- app.py: registers json-pcloud, adds the /auth/pcloud link flow and its
  callback, and a provisioner for the dispatch table.

pCloud is storage, not identity. Google remains the identity provider; the
user links pCloud separately and its access token lives in the browser next to
the Google credentials, never on the server. storage.js now merges the OAuth
handoff into the stored credentials record instead of replacing it, so the two
coexist and a Google re-login does not unlink pCloud.

Deployment prerequisite: register the redirect URIs on the pCloud app at
https://docs.pcloud.com/my_apps/ — pCloud rejects an unregistered redirect_uri.

Closes #96

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RYAzzVJJqrsp6TkeRS4vX
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions 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.

Gatehouse found no issues.

Two unrelated-to-pCloud CI breaks surfaced on this PR:

- ruff 0.16 stabilized PLR0917 (too-many-positional-arguments) out of
  preview, so it began firing on the MCP tool signatures in
  pomodoro_tools.py and todos_plugin.py. Accepted alongside the PLR0913
  ignore those files already carry, for the same reason: agents pass these
  by name and bundling them would obscure the tool schema.
- ruff 0.16 also began formatting fenced code blocks inside Markdown,
  rewriting illustrative snippets in spec documents. Specs are prose, not
  source — excluded, matching gourmand.toml.

Gourmand findings in the new pCloud code are fixed rather than excepted
where the project has no precedent for excepting them:

- generic_names: no exception for this check exists anywhere in the repo,
  so data/result are renamed to body/result_code/stat.
- verbose_comments: comment blocks folded into the docstrings they belong
  on, matching google_drive_transport.py which needs no exception.
- conditional_wrapper in the test mock: replaced the early-return dispatch
  with a response table.

Exceptions added only where precedent is exact: the save_pomodoros_batch
guard clause (verbatim from json_google_drive_storage.py), _find_file
existing to keep the transport interface identical to GoogleDriveTransport,
and the spec-kit Markdown files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RYAzzVJJqrsp6TkeRS4vX

@github-actions github-actions 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.

Gatehouse found no issues.

Gourmand allows no standalone comment blocks in this file (no exception,
matching google_drive_transport.py which has none). The NOT_FOUND_CODES
rationale moves to the module docstring, where it reads better anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RYAzzVJJqrsp6TkeRS4vX

@github-actions github-actions 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.

Gatehouse found no issues.

@fatherlinux
fatherlinux merged commit c4fd9d8 into main Jul 31, 2026
5 checks passed
@fatherlinux
fatherlinux deleted the feature/096-json-pcloud branch July 31, 2026 16:09
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.

json-pcloud storage plugin

1 participant