Docs: marketplace, and clean up new-code warnings/lints

CLAUDE.md gets a Marketplace subsection under Key Conventions (the sync
script is app-embedded and re-uploaded on every sync, never baked into
container/ — pre-flight F9) and the Settings export/import section now
covers marketplace account tokens traveling in ExportedSecrets and the
import preview's warning on global hook and plugin installs.
HOW-TO-USE.md gets a Marketplace section (placed after Shared Claude
Authentication) with its Table of Contents entry (pre-flight N13). The
spec doc's stale keychain service name, gh-login flags and
upload_bytes_to_container signature are amended to match the shipped
code (pre-flight N10).

Also fixes the new marketplace code's remaining build/clippy warnings:
BTreeMap/Sha256/Digest imports in tree.rs gated behind #[cfg(test)]
(their only uses are on MemTree, already test-only), the unused
`pub use marketplace::*` glob re-export dropped from models/mod.rs,
gh_login::strip_ansi marked #[cfg(test)] (production streams through
AnsiStripper instead), and four clippy lints in marketplace test code
(double_ended_iterator_last, cloned_ref_to_slice_refs x2,
single_match). Flushes the unresolved getMarketplaceSyncReport promise
in MarketplaceSection.test.tsx's "opens the Marketplace filtered to
this project" test to remove its act() warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 09:54:24 -07:00
co-authored by Claude Opus 5.5
parent 9588687934
commit 2c1d6d8713
10 changed files with 99 additions and 25 deletions
@@ -41,7 +41,7 @@ publishing to a marketplace from inside Triple-C, a separate OS window for the m
(shared Claude token, gateway keys). Project secrets are restricted to `PROJECT_SECRET_KEYS`.
- Container start: `commands/project_commands.rs` `start_project_container` runs
`docker::sync_bedrock_credentials` after start (≈:1448) — the pattern the marketplace sync follows.
- Exec/upload: `docker/exec.rs` `upload_bytes_to_container(container_id, dest_dir, file_name, data)`,
- Exec/upload: `docker/exec.rs` `upload_bytes_to_container(container_id, dest_dir, file_name, data, mode)`,
`exec_oneshot_streams_as(container_id, user, cmd, env)`, `create_attached_exec_as(…, tty, user)`.
Container user is addressed as `"claude"`. Constant-script + env-data rule: header of
`commands/inspect_commands.rs`.
@@ -141,10 +141,10 @@ pub marketplace_installs: Vec<MarketplaceInstall>, // project-only additions
pub marketplace_disabled: Vec<MarketplaceItemRef>, // (marketplace_id, kind, key) opted out
```
**Secrets.** Token and GhContainer accounts keep their token in the keychain as
`marketplace-account:<id>` (new global helpers in `secure.rs` alongside the gateway ones).
GhHost accounts store nothing: every fetch runs `gh auth token --hostname <host>` so a later
`gh auth refresh`/logout on the host is honoured. Deleting an account deletes its entry.
**Secrets.** Token and GhContainer accounts keep their token in the keychain, one service per
account (`triple-c-marketplace-account-<id>`; new global helpers in `secure.rs` alongside the
gateway ones). GhHost accounts store nothing: every fetch runs `gh auth token --hostname <host>`
so a later `gh auth refresh`/logout on the host is honoured. Deleting an account deletes its entry.
**Effective set for a project** (pure function, unit-tested):
`(global − project.marketplace_disabled) ∪ project.marketplace_installs`, keyed by
@@ -161,7 +161,13 @@ the next sync removes those items from containers; the UI says so before the mar
removed and offers "Forget" to drop the stale entries.
**Export/import.** Accounts (without secrets), marketplaces and install lists go into the existing
export; account tokens follow the existing encrypted-secrets policy of `settings_export.rs`.
export as ordinary `AppSettings` fields; account tokens follow the existing encrypted-secrets
policy of `settings_export.rs` (`ExportedSecrets::marketplace_account_tokens`, keyed by account
id, restored to the keychain before the settings replace). Because the import is untrusted input
and not merely a restore, imported marketplaces and installs are validated on import the same way
the add-marketplace/install commands validate them (host, key pattern, pinned-commit shape), and
the confirmation preview warns whenever the import contains a global hook or global plugin install
— those skip the hook-confirm step an install from the Marketplace tab shows.
## 3. Fetching and signing in
@@ -186,11 +192,14 @@ Hooks' diffs always show the rendered commands.
logged in, tell the user to run `gh auth login` (we do not drive the host's gh interactively).
`gh api user --jq .login` for the display name.
- **GitHub via `gh` in a container** (no host `gh`): user picks a running project; Triple-C runs
`gh auth login --hostname <host> --web --git-protocol https --scopes repo` in an attached pty
exec with `GH_CONFIG_DIR=$(mktemp -d)`, surfaces the one-time code and URL in a dialog (same
shape as `ClaudeAuthModal`), then runs `gh auth token` with the same config dir, stores the
token in the keychain and `rm -rf`s the dir. Cancel tears the exec down. Nothing persists in the
container, so Claude in that container is not logged into the user's GitHub.
`gh auth login --hostname <host> --web --git-protocol ssh --skip-ssh-key --scopes repo` in an
attached pty exec, with `GH_CONFIG_DIR` and `GIT_CONFIG_GLOBAL` both pointed at a temp dir
(`$(mktemp -d)`) — `--git-protocol ssh --skip-ssh-key` avoids gh's "Authenticate Git with your
GitHub credentials?" prompt, which under `https` would otherwise write a credential helper into
`~/.gitconfig`. It surfaces the one-time code and URL in a dialog (same shape as
`ClaudeAuthModal`), then runs `gh auth token` with the same config dir, stores the token in the
keychain and `rm -rf`s the dir. Cancel tears the exec down. Nothing persists in the container,
so Claude in that container is not logged into the user's GitHub.
- **Token**: pasted once, validated via the host's "who am I" API
(GitHub `GET /user`, Gitea `GET /api/v1/user`, GitLab `GET /api/v4/user`; unknown host → test
`ls-remote`-equivalent fetch), stored in the keychain. The token is never returned to the frontend.