Commit Graph
327 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5.5 3dfdafc9ca Marketplace: show path-valued and .lsp.json plugin components whole (PR re-review #4)
A plugin's hooks / mcpServers / lspServers given as a path (or list of
paths) in its catalog entry or plugin.json is resolved inside the plugin
folder and shown as that file's contents; a path escaping the folder or
naming a missing file makes the plugin invalid. The root .lsp.json is
listed next to hooks/hooks.json and .mcp.json. Components are no longer
cut at 64 KiB: they are shown whole up to the 1 MiB manifest cap, and a
plugin whose runnable parts cannot be shown whole is refused instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:20:12 -07:00
shadowdaoandClaude Opus 5.5 973e51f969 Marketplace: use is_multiple_of in describe_size (clippy)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:13:48 -07:00
shadowdaoandClaude Opus 5.5 8ec033f923 Marketplace: match an update to the install pinned at its commit (PR review #8)
InstalledPane's updateFor now requires u.pinned === i.commit, so an install
of the same item at another (unchanged) commit no longer borrows another
install's update and shows a phantom one with an empty diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:13:11 -07:00
shadowdaoandClaude Opus 5.5 fe15f541b9 Marketplace: toast a refused account change and refresh after a saved one (PR review #7)
changeAccount now catches a rejected updateMarketplace (e.g. an account for
another host) and toasts it instead of leaving an unhandled rejection, and
refreshes the marketplace after a successful change so the old fetch error
is replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:13:11 -07:00
shadowdaoandClaude Opus 5.5 c87299dda3 Marketplace: cheaper update checks and single-marketplace refreshes (PR review #10)
compute_updates reads each marketplace's head once (snapshot_head, no
snapshot clone) and opens each cache once, sharing trees across installs
through GitTree::at. refresh_marketplaces(Some(id)) re-pins only that
marketplace (mk::set_pins with only) and returns only its snapshot, which
the frontend merges by id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:12:14 -07:00
shadowdaoandClaude Opus 5.5 e62ca8795a Marketplace: lock each cache on its own; a removed marketplace stays removed (PR review #5, #6)
The single global repo lock becomes one lock per marketplace, and
refresh_pins takes each marketplace's lock only while setting its pins, so
a slow fetch no longer queues installs and refreshes of other marketplaces.

refresh_marketplace now takes the lock first and reads the settings store
under it (a closure, not a copy captured earlier); a marketplace removed
meanwhile gets no cache and no snapshot. Removing a marketplace deletes its
snapshot and cache under the same lock (remove_marketplace_cache).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:10:04 -07:00
shadowdaoandClaude Opus 5.5 da65d51f09 Marketplace: review a plugin's catalog entry and confirm what it runs (PR review #3, #4)
A plugin's update diff now includes its marketplace.json entry as a
pretty-printed "marketplace.json entry" file, so inline hooks, MCP servers
and commands are reviewed like any file. CatalogItem gains
plugin_components (entry / plugin.json runnable keys, hooks/hooks.json,
.mcp.json, commands/), and installing a plugin now goes through
PluginConfirmModal listing them. The import-preview warnings describe
that confirmation accurately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:08:15 -07:00
shadowdaoandClaude Opus 5.5 d51b54774b Marketplace: edit only the marketplace fields of a project (PR review #2)
ProjectsStore gains update_marketplace_fields / update_all_marketplace_fields,
which read-modify-write installs and opt-outs under the store's own lock.
Install, uninstall, update, forget and set_global_item_disabled use them
instead of writing back a whole Project read earlier, so a concurrent
start's status/container_id change is no longer overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:04:51 -07:00
shadowdaoandClaude Opus 5.5 e805c29c70 Marketplace: refuse an update to a version that is not installable (PR review #1)
Install and update now share ops::installable_at_head: the reviewed head
must still be the head and the item's catalog entry there must be valid.
The old update check (item_files) never parsed hook.json, so an upstream
hook with an unknown event could be pinned and then held by every sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:03:14 -07:00
shadowdaoandClaude Opus 5.5 14852ead65 Marketplace: check blob sizes from the object header before loading (PR review #9)
GitTree::read_file now takes a cap and reads the object's size from its
header first, so a blob over MAX_MANIFEST_BYTES / MAX_ITEM_BYTES is refused
without being inflated, and the blob is taken rather than cloned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:01:48 -07:00
shadowdaoandClaude Opus 5.5 f2bb092586 Marketplace sync: keep installs the host could not build (final review M3)
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 3m49s
Build App (Preview) / test (pull_request) Successful in 5m37s
Build App (Preview) / build-windows (pull_request) Successful in 7m20s
Build App (Preview) / build-linux (pull_request) Successful in 8m7s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
An install the host skipped (pinned commit missing from the cache, cache
unreadable, item failing a tightened validation rule) never reached the
manifest, so sync.sh treated it as deselected and deleted it from the
container. The manifest now carries `held`: the state ids of such
installs ("plugin:<slug>/<key>" for plugins). The script counts them as
still selected and carries their records forward, as it already does
for items that fail inside the container. A removed marketplace is the
one skip that still removes; a malformed `held` list changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:16:27 -07:00
shadowdaoandClaude Opus 5.5 5829c42f0f Marketplace: derive the plugin slug from the id only (final review M4)
The slug (plugin marketplace "triple-c-<slug>", plugin tree
"plugins/<slug>/") was built from the editable display name. After a
rename the next sync registered the new marketplace, skipped the plugin
install because the state's commit matched, then removed the old
marketplace: the plugin was gone while the report said nothing changed.

marketplace_slug now takes the id only ("mp-<id8>"). With plugin state
kept per slug (I1), containers synced with the old "<name>-<id8>" slugs
move over on their next sync: plugins are installed under the new name,
the old copies uninstalled and the old registration dropped. A sync
script test covers that migration (it fails on the pre-I1 script).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:14:30 -07:00
shadowdaoandClaude Opus 5.5 19ae92d4f8 Marketplace fetch: offer the token only to the marketplace host (final review M1)
The credential callback answered every credential request. gix follows
a redirect of the initial handshake and asks for credentials for the
redirect target, so the token could be sent to another host. The
callback now answers only when the request's scheme, host and port
match the marketplace URL (gix's own URL normalisation, host compared
case-insensitively); anything else gets no credential.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:12:47 -07:00
shadowdaoandClaude Opus 5.5 dd019cf2c0 Marketplace: pin the commit the user reviewed (final review I2)
Install and update pinned whatever the marketplace head was when the
click landed, so a background refresh between review and click could
pin content nobody saw (including a hook's shell commands).
install_marketplace_item and update_marketplace_item now take
expected_commit and refuse with "changed since you reviewed this item —
review it again" unless it is still the head. The UI passes the head the
selected item was read at (Browse), the head frozen with a pending hook
confirm (whose commands are frozen too), and the head of the accepted
diff (Installed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:11:19 -07:00
shadowdaoandClaude Opus 5.5 f2ebddd073 Marketplace sync: track plugin state per marketplace (final review I1)
Two marketplaces shipping a plugin of the same name shared one
"plugin:<key>" state record, so every sync reinstalled one copy and
reported it updated, and removing one marketplace never uninstalled its
copy. Plugin state ids are now "plugin:<slug>/<key>"; the slug and key
for an uninstall are derived from the id and re-validated. Older
"plugin:<key>" records are migrated using their recorded slug, so
existing installs are neither reinstalled nor orphaned. Reports keep
"plugin:<key>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:07:57 -07:00
shadowdaoandClaude Opus 5.5 2c1d6d8713 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>
2026-09-27 09:54:24 -07:00
shadowdaoandClaude Opus 5.5 9588687934 Merge Tasks 12–16 (marketplace frontend) into feat/marketplace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:48:48 -07:00
shadowdaoandClaude Opus 5.5 6cf9664dc8 Marketplace: warn on imported global plugins; tidy import follow-ups
- The import preview counts global plugin installs and warns on them:
  a plugin can bring hooks and MCP servers into every container and an
  imported install skips the confirm step, like a hook.
- Item keys, hosts and branches in errors are quoted with {:?} and
  capped, since they can come from an import file.
- After an import, caches and snapshots of marketplaces the import
  dropped are removed (under the repo lock) and pins are refreshed for
  the imported installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:46:56 -07:00
shadowdaoandClaude Opus 5.5 89859b9a3c Marketplace gh login: read gh 2.101's device-code wording
gh 2.101.0 (the image's) prints "! One-time code (XXXX-XXXX) copied to
clipboard" and "Press Enter to open https://github.com/login/device in
your browser...". parse_device_prompt only knew "one-time code:", so no
code event went out and Enter was never pressed: gh sat at its prompt
until the 10-minute timeout. Match the label case-insensitively, accept
":" or "(" before the code, and require something after it so a code cut
by a frame boundary is not taken early.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:44:25 -07:00
shadowdaoandClaude Opus 5.5 f4153dce42 Marketplace: Tauri commands, store-owned fields and startup refresh
The 21 marketplace commands, registered and granted; marketplace fields
kept store-owned in update_settings/update_project; a background refresh
of every marketplace at app start.

- apply_marketplace_now emits marketplace-sync-finished per project (F4).
- Settings export carries marketplace account tokens in ExportedSecrets
  (account id -> token) and import restores them; imported accounts,
  marketplaces and global installs are validated with the commands' own
  rules before anything is written. The import preview discloses the
  marketplace count, token count and global hook installs, and warns on
  the latter (F10).
- refresh_pins and cache removal hold the repo lock (F11).
- ops::validate_host/validate_branch delegate to auth::valid_host and
  git::valid_branch (F13).
- A finished gh container login frees only its own cancel slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:41:59 -07:00
shadowdaoandClaude Opus 5.5 7a55c11b31 Merge Task 10 (gh sign-in inside a container) into feat/marketplace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:32:54 -07:00
shadowdaoandClaude Opus 5.5 d9f143cdb3 Marketplace gh login: tear down the container login on every failure
A lost stream or a failed Enter write returned without killing the
in-container gh, leaving it polling with a temp GH_CONFIG_DIR that would
receive the token. The output loop is now drive_login (generic over the
stream, writer and emitter, so it is unit-tested without Docker), and
its result goes through cleanup_on_error, so every ending except a token
read back runs the pkill. Neutral wording for the shared ANSI stripper's
overflow warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:31:17 -07:00
shadowdaoandClaude Opus 5.5 310d55eb37 Marketplace: sync projects into their containers on start
Waits for the entrypoint, uploads the payload and the sync script, runs it
as claude and stores its report (payload skips merged in). The start hook
spawns the sync in the background; a per-project lock serialises syncs of
one project (pre-flight F11b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:30:58 -07:00
shadowdao 1168a0c56b Merge Task 8 (container sync script) into feat/marketplace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	app/src-tauri/src/marketplace/mod.rs
2026-09-27 09:28:31 -07:00
shadowdaoandClaude Opus 5.5 d84637fd39 Marketplace: GitHub sign-in through gh inside a container
Drives `gh auth login --web` in a running project container over an
attached pty, with GH_CONFIG_DIR/GIT_CONFIG_GLOBAL in a temp dir that is
removed on exit (also on HUP/INT/TERM), emits the one-time code and
redacted output lines, and returns the token read back between markers.
Host validation reuses auth::valid_host plus a no-port check (F13); a
cancel or timeout also pkills the in-container login (N9). Reuses the
setup-token flow's AnsiStripper, push_capped_tail and Enter delay, made
pub(crate) without behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:27:22 -07:00
shadowdaoandClaude Opus 5.5 7fb2190211 Marketplace sync script: review fixes (round 2)
Removal only derives a path from an exact <kind>:<key> state id with a known
kind; any other record is dropped with an error and nothing is deleted
(an id like "skill" used to remove ~/.claude/skills/skill). Invalid plugin
records are dropped too, and a failed chmod 600 on settings.json is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:27:10 -07:00
shadowdaoandClaude Opus 5.5 5cbb4591fe Marketplace sync script: review fixes (round 1)
- Validate manifest structure up front; malformed items are skipped with a
  reason instead of aborting extraction; an unreadable manifest changes
  nothing (no removals).
- Empty/whitespace settings.json reads as {}; non-object settings are left
  untouched; hook installs/updates/removals are reported and recorded only
  once their entries are actually merged; mv failures are checked.
- Dangling symlinks at user paths count as occupied.
- Removal paths are derived from kind+key, never taken from state.json.
- A symlinked settings.json is written through, not replaced.
- mktemp failure emits a JSON report instead of exiting silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:24:16 -07:00
shadowdaoandClaude Opus 5.5 7f3fe8cded Marketplace: build the per-project payload tar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:21:11 -07:00
shadowdaoandClaude Opus 5.5 b73067019f Marketplace: container sync script and its tests
Constant POSIX sh + jq script (embedded via include_str!) that applies the
payload into ~/.claude, tracks ownership in state.json, never overwrites
user-owned files, merges hook entries surgically, drives claude plugin and
prints a JSON SyncReport. Also: settings.json kept 0600 (pre-flight N11),
payloads containing symlinks are refused, slugs parsed via @tsv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:17:23 -07:00
shadowdaoandClaude Opus 5.5 9a1833d792 Marketplace: item diff, manager, refresh and update detection
Adds diff::item_diff (similar), MarketplaceManager with snapshots,
persisted sync reports, the gh-login slot and a repo lock held across
fetches (pre-flight F11a), refresh_marketplace, load_cached_snapshot,
compute_updates, pins_by_marketplace, head_for, and the GitFixture test
helper on top of git::test_support (F3). AppState gains marketplace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:17:17 -07:00
shadowdaoandClaude Opus 5.5 51490a534e Marketplace auth: never follow redirects in token validation
Review fix round 1 for Task 5. reqwest's default redirect policy only
strips Authorization/Cookie/Proxy-Authorization/WWW-Authenticate on a
cross-host hop, so GitLab's PRIVATE-TOKEN header (and Authorization on
an https->http same-host downgrade) would have followed a redirect to
an attacker-controlled target. The client now disables redirects
outright, and a 3xx response is treated as "not this kind of host"
rather than an error. Also adds the missing N17 test for a malformed,
credential-bearing URL, and clarifies two doc comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:12:19 -07:00
shadowdaoandClaude Opus 5.5 126d7148ac Marketplace: account credentials, token validation and fetch-error advice
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:08:44 -07:00
shadowdaoandClaude Opus 5.5 28c8f0479b Project Config: Marketplace section with per-project opt-out and last sync report
Shows items this project gets from "All projects" installs (with a
per-item opt-out switch saved through setGlobalItemDisabled, since
opting out doesn't require a stopped container) and this project's own
installs. Fetches the last sync report on mount and refetches it when
marketplace-sync-finished fires for this project (N7, preflight), so
Apply Now and container-start syncs don't leave it stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:08:12 -07:00
shadowdaoandClaude Opus 5.5 e7ee62b456 Marketplace: gix cache with credentialed fetch, pins and GitTree
Anonymous fetches of private repos map to Auth, error text drops gix
source locations and names the innermost network cause, and
valid_branch is pub(crate) for the add form (pre-flight F1, F2, F13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:04:00 -07:00
shadowdaoandClaude Opus 5.5 f3909084f6 Marketplace UI: accounts — gh on host, gh in a container, access tokens
Per preflight F5, Remove is disabled with a hint for an account a
marketplace uses rather than offering a confirm modal that promises a
removal the backend refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:03:38 -07:00
shadowdaoandClaude Opus 5.5 487443c27c Marketplace UI: installed list, update diff review, apply now
Applies preflight rulings F4, F7, F8, N5: Apply now's toast shows only
the success/info summary (the marketplace-sync-finished event listener
already toasts per-project errors/skips, so this avoids a double toast);
row removal passes the bare MarketplaceItemRef rather than the full
MarketplaceInstall; UpdateDiffModal shows a hook's rendered commands at
head above the file diff so an update is reviewed the same way an
install is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:58:38 -07:00
shadowdaoandClaude Opus 5.5 b09f811ac1 Marketplace: validate tree entry names and cap depth/manifest size
Fix round 1 from PR review of the tree/catalog parsing:

- collect_dir now rejects an entry whose name is ".", "..", empty, or
  contains "/", "\" or NUL before it becomes part of an item's rel_path —
  a crafted git tree could otherwise walk a file outside the item's own
  folder once that path is joined against the item root downstream.
- collect_dir caps recursion at 32 directory levels and counts
  directories (not just files) toward MAX_ITEM_FILES, so a tree that is
  wide or deep rather than merely file-heavy is still bounded.
- hook.json and plugins/.claude-plugin/marketplace.json are now rejected
  unparsed above 1 MiB, rather than handed to serde_json regardless of
  size.

A pre-read size query (checking a blob's size before reading it) is
deferred per controller ruling — this round reads the blob and checks
its length before parsing, which is enough for the JSON-parsing DoS
shape being closed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:55:32 -07:00
shadowdaoandClaude Opus 5.5 3c12a2fc89 Marketplace UI: browse, item detail, install controls, hook confirmation, add marketplace
Implements Task 13: BrowsePane (marketplace list, kind/search filters, item
detail), InstallControls (global/per-project install, opt-out, hook confirm
gate), HookConfirmModal, AddMarketplaceModal, and ItemDetail. Also applies
pre-flight ruling F6: a per-marketplace Remove button with a confirm dialog
(mp.remove) warning that surviving installs become "Source removed" and can
be dropped via Forget on the Installed tab, plus an inline account
reassignment select (updateMarketplace + reloadState).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:54:02 -07:00
shadowdaoandClaude Opus 5.5 2e62728b06 Marketplace: repo tree view and catalog parsing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:48:31 -07:00
shadowdaoandClaude Opus 5.5 d23d0a44c5 Marketplace UI plumbing: wrappers, singleton tab, settings section, view shell
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:48:06 -07:00
shadowdaoandClaude Opus 5.5 d419d0a6b4 Marketplace: data model, settings and project fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:40:58 -07:00
shadowdaoandClaude Opus 5.5 ece0d74afb Settings: let the shared-auth buttons wrap inside the sidebar
Re-authenticate, Revoke and Check snapshot images are each nowrap and
together wider than the settings sidebar, so the last one ran outside
its container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 06:41:43 -07:00
jknapp 292fc907fb Shared login: press Enter separately from the pasted code (#64)
Build App / compute-version (push) Successful in 4s
Secret Scan / scan (push) Successful in 4s
Build App / build-macos (push) Successful in 2m45s
Build App / build-windows (push) Successful in 5m35s
Build App / build-linux (push) Successful in 7m33s
Build App / create-tag (push) Successful in 10s
Build App / sync-to-github (push) Successful in 1m13s
2026-09-27 13:29:29 +00:00
jknappandClaude Opus 5.5 0d117e97fe Add Auto permission mode (#63)
Build App / compute-version (push) Successful in 3s
Build Container / build-container (push) Successful in 1m58s
Secret Scan / scan (push) Successful in 4s
Build App / build-macos (push) Successful in 3m4s
Build App / build-windows (push) Successful in 5m48s
Build App / build-linux (push) Successful in 5m10s
Build App / create-tag (push) Successful in 4s
Build App / sync-to-github (push) Successful in 2m27s
Adds Claude Code's auto permission mode as a fifth option between Accept Edits and Bypass, across terminals, resumed sessions, the tab badge, the scheduler task runner and docs. Warns that Auto falls back to prompting when unavailable for the model/backend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 17:02:06 +00:00
jknappandClaude Opus 5.5 8305c96e20 Terminal file viewer/editor + per-window app-command lockdown (#60)
Build App / compute-version (push) Successful in 7s
Secret Scan / scan (push) Successful in 8s
Build App / build-macos (push) Successful in 2m53s
Build App / build-linux (push) Successful in 5m12s
Build App / build-windows (push) Successful in 5m15s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 1m5s
Clicking a file path in Claude's terminal output now opens the file in its own window with a CodeMirror 6 editor. The editor highlights the target line, live-reloads while the file changes, and saves explicitly with hash-based conflict detection. The viewer commands are gated by window label.

Every app command is now ACL-gated per window through a Tauri AppManifest. build.rs checks the handler list against the capability files and fails the build on any mismatch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:05:50 +00:00
jknapp 3537b234d8 Make links in Claude's output clickable (#59)
Build App / compute-version (push) Successful in 6s
Secret Scan / scan (push) Successful in 6s
Build App / build-macos (push) Successful in 2m44s
Build App / build-windows (push) Successful in 4m58s
Build App / build-linux (push) Successful in 5m51s
Build App / sync-to-github (push) Successful in 8s
Build App / create-tag (push) Successful in 9s
Reviewed three times. Rounds 1 and 2 each found a real hole in the gate -- a plain click opened links, then a selection gesture did -- both addressed. The attacker-controlled mouse mode is recorded as a known residual rather than claimed closed.

Still unverified on a real desktop: double-click and drag-select across a link in both tracking states.
2026-09-19 03:20:15 +00:00
shadowdaoandClaude Opus 5 83c9c24951 test: give two synthesised clicks the detail a real click carries
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 9s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m43s
Build App (Preview) / build-linux (pull_request) Successful in 7m58s
Build App (Preview) / build-windows (pull_request) Successful in 4m54s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
The previous commit tightened the gate's click-count check from `> 1` to
`!== 1`, which two tests in the wiring block did not survive: they built
`new MouseEvent("click", { button: 0 })` directly rather than through the
`click()` helper, so `detail` defaulted to 0 and the gate refused them.

The gate is right and the tests were wrong -- a mouseup derived from a real
click always carries `detail >= 1`, and 0 is exactly the synthetic-event
shape the tightening was for. Both now pass `detail: 1`.

I pushed the previous commit without noticing this, having read a truncated
test summary that hid the failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 20:08:36 -07:00
shadowdaoandClaude Opus 5 c6f9c1d43f fix: tighten the click-count check and stop three comments overstating
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 3s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m44s
Build App (Preview) / build-linux (pull_request) Successful in 6m6s
Build App (Preview) / build-windows (pull_request) Successful in 5m0s
Build App (Preview) / prune-previews (pull_request) Successful in 4s
Third-round review polish; no behaviour change beyond the first item.

`detail > 1` was justified in a comment by noting a synthesised event
carries `detail` 0 -- which is an argument for letting untrusted synthetic
events through the click-count half of the gate. A mouseup derived from a
real click always carries `detail >= 1`, so the check is now `!== 1`.
Nothing in the container can dispatch a DOM event, so this is hardening
rather than a hole; the comment now says that instead of the reverse.

Three comments claimed more than they hold. The selection check's
paragraph read as though it caught every copy gesture: it sees a drag only
once the drag has spanned a cell, so a press and release inside one
character cell -- or a drag walked back to its start -- still opens the
link. That is the gap the rejected mousedown/mouseup distance check would
have closed, and it is now recorded beside the reason for rejecting it.

`?1002l` was described as taking effect synchronously with the write; it
takes effect when xterm parses it, on its queued write task. And
`modifierPromised` was described as written on every hover, when `hover()`
clears and returns early with no host element -- which leaves it false, the
stricter direction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 20:07:59 -07:00
shadowdaoandClaude Opus 5 593b8168eb fix: a selection is not a request to leave the app
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 7s
Build App (Preview) / create-release (pull_request) Successful in 4s
Build App (Preview) / build-macos (pull_request) Successful in 2m42s
Build App (Preview) / build-linux (pull_request) Successful in 6m39s
Build App (Preview) / build-windows (pull_request) Successful in 5m0s
Build App (Preview) / prune-previews (pull_request) Successful in 2s
Re-review found the gate did not cover the gesture users actually make.
xterm's `Linkifier._handleMouseUp` has no click-count check, no distance
threshold and no timestamp, so it activates on the mouseup that *ends a
selection* as readily as on a click. Double-clicking a word or dragging
across a few characters inside an OSC 8 link therefore opened the browser.

Worse with a program holding the mouse: the only way to select text there
is Shift/Option+drag, which is byte-identical to the gesture the gate
accepted as a deliberate request to open. A container wrapping each output
row in a link would have harvested every legitimate copy.

`term.hasSelection()` is the load-bearing check: a drag is one press and
one release, so its click count is 1 and `detail` cannot see it. `detail >
1` is belt-and-braces for the case where the selection came out empty, and
for not depending on the selection model being written before the
Linkifier's listener runs -- it is, but the check costs nothing. Drag
distance was rejected rather than forgotten: xterm hands `activate` only
the mouseup, so measuring it means binding our own listener and keeping a
second source of truth about one gesture.

The hover card's promise is now sticky. The hint was computed once at hover
while the gate re-read the mode at mouseup, so a card reading "Shift+click
to open" could be on screen while a bare click opened the link. The gate
now requires the modifier if either the card asked for it or the live mode
does.

The same gate is applied to the WebLinksAddon branch, which had none. That
also closes a real bypass: `OscLinkProvider` drops non-http(s) OSC 8
targets before `linkHandler` sees them, so a `javascript:` target with an
`https://evil.tld` label fell through to WebLinks and opened ungated.

What is not closed, and is now recorded rather than papered over: the mouse
mode is a permission the container grants itself. It can drop tracking
before the pointer arrives and hold it off through the click. The selection
and click-count checks hold either way, so the mass-harvest variant is
gone, but the real fix needs a signal the container cannot write and this
pane does not have one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 20:00:42 -07:00
shadowdaoandClaude Opus 5 a3840f7263 fix: say which check failed, and stop claiming an order we do not use
Secret Scan / scan (push) Successful in 5s
Build App (Preview) / compute-version (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / build-macos (pull_request) Successful in 2m41s
Build App (Preview) / build-windows (pull_request) Successful in 4m53s
Build App (Preview) / build-linux (pull_request) Successful in 4m58s
Build App (Preview) / prune-previews (pull_request) Successful in 4s
Two accuracy defects from re-review, both the same class as the bug this
branch exists to fix.

`probe_failed` rendered every failure as "This project's container could
not be inspected", but only two of the four readings are about the
container -- the others are the base image and the snapshot. A malformed
base image name in settings therefore pointed the user at the wrong object.
The sentence now names the check rather than the container.

The doc claimed "the first error wins, in call order". It does not: the
checks run container_id, base_image_id, container_running, while the daemon
is called in a different order entirely. The priority is deliberate -- it
puts the reading that stopped the probe first -- so the comment now says
that, instead of describing an order the code does not use.

The test guarding the first point asserted the message does not contain
"Docker", using a synthetic payload. The real bollard error for that case
is "Docker responded with status code 400: invalid reference format", so
the assertion passed only because the payload was invented. It now uses the
real shape and asserts what actually matters: that nothing we add claims
the daemon was unreachable or names the container.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 19:53:52 -07:00