Commit Graph

50 Commits

Author SHA1 Message Date
the_og 37b3c8f5d0 catalog: trust the real signing key
CI / Tests (py3.12 / windows-latest) (push) Successful in 23s
CI / Tests (py3.12 / ubuntu-latest) (push) Successful in 10s
CI / Tests (py3.13 / ubuntu-latest) (push) Successful in 10s
CI / Lint (ruff) (push) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (push) Successful in 9s
Adds the Ed25519 public key generated by the Catalog Console (#62),
replacing the b"\x00"*32 placeholder, and imports base64 (the key line
referenced it without the import, so bcc_core failed to load at all).

Verified end to end against the signature the Console pushed in b08cf21:
signature verifies, catalog validates clean, resolve_catalog accepts the
bundled copy (19 servers), and a single-byte tamper is rejected.
2026-07-12 18:25:49 -04:00
Cowork Agent 48904c7787 feat: MCP server catalog core -- signed, validated, resolvable (#10, #61)
CI / Lint (ruff) (pull_request) Successful in 12s
CI / Tests (py3.12 / windows-latest) (pull_request) Failing after 17s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 14s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 39s
Phase 1 of the MCP server catalog: pure, GUI-free core functions plus the
seed data/catalog.json (20 servers). No GUI wiring in this PR -- bcc.py is
untouched; a follow-up PR adds the picker dialog.

- load_catalog(): strict json.loads ONLY. The lenient repair pipeline
  (repair_json_text / parse_pasted_json*) is never used on catalog bytes,
  by design and by comment, so a signature always authenticates exactly
  what gets parsed.
- validate_catalog(): rejects the whole file (not per-entry) on: bad
  schema/version types, missing tier-appropriate fields (basic needs
  config.command+args, link-only needs docs_url and no config), a
  command allowlist (npx/uvx/docker/node/python/python3 only), -e/--eval/-c
  denial for node/python, --privileged and root/$HOME volume-mount denial
  for docker, non-empty env_required values (hard rejection -- secrets
  never ship in the catalog), secret-looking args (reuses
  _TOKEN_PREFIXES/_is_secret_value rather than reimplementing), non-https
  URL fields, and non-ASCII code points in id/command/args (homoglyph
  defence).
- verify_catalog_signature(): Ed25519 via the cryptography package,
  domain-separated message (the literal prefix "bcc-catalog-v1|" + raw
  bytes), accepts a match against any key in CATALOG_PUBKEYS
  (rotation-ready), never raises.
- resolve_catalog(): picks the highest version among bundled/cached/remote
  candidates that EACH independently pass verify + validate -- the bundled
  catalog gets no implicit trust, closing the hole where an unsigned
  payload merged to main would win on being local. Anti-rollback (never
  regress below the best verified candidate already in hand) and
  anti-freeze (reject a jump of more than 1000 versions) built in.
- catalog_entry_to_paste_json() / config_has_unfilled_placeholders(): small
  pure helpers the future GUI dialog will use to feed a catalog pick into
  the existing paste-import path and to gate Save on unfilled placeholder
  tokens.

data/catalog.json: the provided 20-server seed, with a signed_at field
added at the top level (lives inside the signed payload once real signing
lands in #62). Wired into bcc.spec's PyInstaller datas so it bundles into
the frozen app.

Security requirements from the issue, and where they landed:
- Catalog bytes never touch the lenient JSON repair path -- enforced by
  load_catalog()'s strict json.loads and a comment warning against wiring
  it in later.
- env_required values are a hard rejection when non-empty, not a warning.
- Secret-looking args are rejected at validation time, reusing the
  existing secret-detection helpers instead of duplicating them.
- Non-ASCII id/command/args rejected (typosquat/homoglyph defence).
- URL fields restricted to https://.
- Ed25519 signature verification is domain-separated and never raises.
- The bundled catalog is verified at runtime exactly like remote/cached --
  no implicit trust for being local.
- Anti-rollback and anti-freeze bounds on resolve_catalog's version
  comparison.

Tests: 42 new tests added to tests/test_core.py (full suite: 239 passed,
1 pre-existing unrelated skip). ruff check and ruff format --check both
clean.
2026-07-12 17:32:50 -04:00
Cowork Supervisor 4afe21666d release: bump version to 1.3.0
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 21s
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
Named server sets (#52), project .mcp.json discovery (#53), schema
lint (#54), Ctrl+S + enable/disable-all (#55).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:02:38 -04:00
the_og 47c95ac006 Merge pull request 'feat: named server sets — save/apply the Active/Disabled split (#52)' (#56) from feat/52-server-sets into main
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (push) Successful in 10s
CI / Tests (py3.12 / windows-latest) (push) Successful in 20s
CI / Tests (py3.13 / ubuntu-latest) (push) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (push) Successful in 9s
2026-07-12 14:00:19 -04:00
the_og 6b22ad26f0 Merge pull request 'feat: structural schema lint for server definitions (#54)' (#58) from feat/54-schema-lint into main
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (push) Successful in 9s
CI / Tests (py3.12 / windows-latest) (push) Successful in 20s
CI / Tests (py3.12 / ubuntu-latest) (push) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (push) Successful in 9s
2026-07-12 14:00:12 -04:00
Cowork Supervisor ac2e73e9d7 feat: named server sets — save/apply the Active/Disabled split (#52)
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 21s
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 8s
Sets live in the config under _bccServerSets (bcc-owned, ignored by
Claude, travels with the file). Apply enables exactly the set's members
and parks the rest; vanished members are reported, not fatal. GUI row:
set combo + Apply + Save set… + delete.

Closes #52

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:55:36 -04:00
Cowork Supervisor 82ff149373 feat: structural schema lint for server definitions (#54)
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 19s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 8s
Closes #54

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:55:22 -04:00
Cowork Supervisor 520b1b2ffd feat: discover Claude Code project .mcp.json configs as profiles (#53)
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 20s
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 9s
Closes #53

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:53:38 -04:00
Cowork Supervisor 0ef4586698 release: bump version to 1.2.1
First release actually shipping the v1.2.0 feature set (the v1.2.0
tag's release run was cancelled and produced no assets) plus the
audit fixes #32-#40 and the Windows process-tree kill (#13).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:31:36 -04:00
Cowork Supervisor 1384ed9703 fix: kill the whole process tree on Windows spawn-test timeout (#13)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 20s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 8s
Popen.kill() only terminated the direct child, so runner-style commands
(npx -> node -> server) leaked the real server process on every Windows
spawn test. taskkill /PID <pid> /T /F walks the descendant tree. Also
sets CREATE_NO_WINDOW on the spawned test process so the windowed exe
doesn't flash a console per test.

Closes #13

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:30:02 -04:00
the_og 6dacc26057 Merge pull request 'fix: harden restart_claude_desktop — Linux refusal, macOS quit-wait, Windows MSIX guard (#33)' (#43) from fix/33-restart-hardening into main
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10) (push) Successful in 9s
CI / Tests (py3.12) (push) Successful in 9s
2026-07-12 13:00:30 -04:00
Cowork Supervisor c7b2c90518 fix: spawn_test never raises — coerce env/command to str, wrap unexpected errors (#34)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
Closes #34

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:59:19 -04:00
Cowork Supervisor c493aa0c84 fix: harden restart_claude_desktop across platforms (#33)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 9s
CI / Tests (py3.12) (pull_request) Successful in 10s
- Linux (and any non-desktop platform): refuse instead of 'pkill claude',
  which substring-matched running Claude Code CLI sessions and relaunched
  the CLI, not a desktop app. New restart_supported() gates the button.
- macOS: wait (<=5s) for the old instance to exit before 'open -a Claude'
  so the relaunch can't re-activate the dying process. Runs off the UI
  thread via a RestartWorker.
- Windows: verify the Start-menu shortcut exists BEFORE taskkill, so an
  MSIX/Store install is never killed without a relaunch path.

Closes #33

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:58:15 -04:00
Cowork Supervisor f4d4301c26 Merge remote-tracking branch 'origin/main' into feat/server-list-ux
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
2026-07-08 11:55:36 -04:00
Cowork Supervisor 668fb903d0 feat: server search/filter + test-all health column (#27, #28)
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 7s
2026-07-08 11:53:31 -04:00
Cowork Supervisor 8c456c9a89 feat: detect MSIX-virtualized Claude config path + warn (#7)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
2026-07-08 11:51:23 -04:00
Cowork Supervisor 8c718387c0 chore: bump version to 1.2.0 for release
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
2026-07-07 22:14:49 -04:00
Cowork Supervisor 15a30fb986 Merge branch 'feat/18-19-about-update-checker' into integration/v1.2.0
# Conflicts:
#	bcc.py
#	bcc_core.py
#	tests/test_core.py
2026-07-07 22:13:29 -04:00
Cowork Supervisor c56dec8051 Merge branch 'feat/9-restart-claude-desktop' into integration/v1.2.0
# Conflicts:
#	bcc_core.py
#	tests/test_core.py
2026-07-07 22:12:35 -04:00
Cowork Supervisor 70b865be8f Merge branch 'feat/6-log-viewer' into integration/v1.2.0
# Conflicts:
#	bcc_core.py
#	tests/test_core.py
2026-07-07 22:12:17 -04:00
Cowork Supervisor 8fdcbda681 Merge branch 'feat/17-stale-size-hardening' into integration/v1.2.0 2026-07-07 22:10:59 -04:00
the_og 2d9fb083dc Fix: correct smart-quote/nbsp characters mangled in previous push
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
2026-07-07 20:56:14 -04:00
the_og 4ab3c3b00a Fix: rebuild from clean main (previous push included unrelated contaminated content)
CI / Lint (ruff) (pull_request) Failing after 8s
CI / Tests (py3.10) (pull_request) Failing after 8s
CI / Tests (py3.12) (pull_request) Failing after 7s
2026-07-07 20:51:26 -04:00
the_og 256827eaf3 fix: use \xa0 escape instead of a literal NBSP byte (transmission-safe, same behavior); drop stray trailing blank line 2026-07-07 20:38:35 -04:00
the_og 7c8fd6d0bb fix: use explicit backslash-u-0-0-a-0 escape for non-breaking space literal 2026-07-07 20:34:53 -04:00
the_og 0ffc6a1fb6 chore: restore trailing newline in bcc_core.py (attempt 2) 2026-07-07 20:32:36 -04:00
the_og 346d0aabb6 fix: use   escape for non-breaking space (byte-exact transcription fix) 2026-07-07 20:30:35 -04:00
the_og 5d59c1c423 fix: restore actual file content (previous commit had placeholder text by mistake) 2026-07-07 20:30:03 -04:00
the_og 82cec27c11 fix: use chr(0xA0) instead of a literal/escaped NBSP
Two prior attempts to fix this line via a literal or  -escaped
non-breaking space both silently reverted back to a no-op regular-space
replace during transcription. Using chr(0xA0) instead removes any
non-ASCII or backslash-escape character from the source line entirely.
2026-07-07 20:27:17 -04:00
the_og 6c51bac1e0 feat: restart_claude_desktop() core logic (#9)
Platform-abstracted restart: macOS uses pkill -x Claude + open -a Claude,
Windows uses taskkill + relaunch via the Start-menu shortcut, Linux uses
pkill claude + a detached Popen relaunch. Not finding a running process is
not an error -- only a failed relaunch is reported as failure.

Also adds profile_targets_claude_desktop() to distinguish Claude Desktop
profiles (claude_desktop_config.json) from Claude Code profiles, used to
gate the restart button in the GUI.
2026-07-07 20:26:53 -04:00
the_og fe66d53e9f feat: __version__ constant + notify-only update checker logic (#19), version source for About dialog (#18)
- Add `__version__ = "1.1.0"` as the single source of truth (matches pyproject.toml).
- Add parse_version()/is_newer_version() for numeric (never lexical) version
  comparison, handling v-prefix, pre-release suffixes, and malformed input.
- Add fetch_latest_release(): reads the public Gitea releases API
  (anonymous, no token) and returns {version, url} or None on any failure.
  Never downloads or touches a binary — metadata only.
2026-07-07 20:25:33 -04:00
the_og 8d90ab449d feat: add server_log_path() for in-app MCP log viewer (#6) 2026-07-07 20:24:10 -04:00
the_og 9760b1537e fix: restore non-breaking-space literal lost in transcription
The previous commit accidentally normalized the non-breaking space
(U+00A0) in _normalize_unicode's replace() call to a regular space
during a copy/paste, turning that replace() into a no-op. Use an
explicit   escape instead of the literal character so it can't
be silently corrupted again.
2026-07-07 20:23:44 -04:00
the_og 0f1cdbef3c bcc_core.py: fix non-breaking-space byte lost in transcription 2026-07-07 20:22:45 -04:00
the_og 165c65be5f bcc_core.py: add resolve_name_collision() for paste/import name-collision handling 2026-07-07 20:19:25 -04:00
the_og 3c65657d2f feat: harden stale-file detection with mtime+size fingerprint (#17)
Bare mtime equality can miss a concurrent external write that lands
within the filesystem's mtime resolution (same-second writes), or
where the writer restores the original mtime. Add config_fingerprint()
returning a (mtime, size) ConfigStat pair; the stale-file check in
bcc.py now compares both fields instead of mtime alone. config_mtime()
is kept as-is (still used/tested independently).
2026-07-07 20:17:57 -04:00
AJ f4648b3c06 feat: warn when a secret-shaped value is found in args instead of env (#1r)
CI / Lint (ruff) (pull_request) Successful in 8s
CI / Tests (py3.10) (pull_request) Successful in 10s
CI / Tests (py3.12) (pull_request) Successful in 9s
Adds `args_secret_warning(data)` to bcc_core — returns a warning string
when any arg positional value looks like a raw credential (token prefix,
value following a secret-named flag, or URL with embedded user:pass like
postgres://user:pass@host).  `--flag=value` inline forms are intentionally
skipped (the flag name already labels the value).

Adds `secret_warn` QLabel in ServerEditor's stdio page; shown/hidden by
`_check_args()` on every field change, and cleared on deselect or
stdio→remote type switch.  Non-blocking — save path is not touched.

Closes #1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 11:59:17 -04:00
AJ 9fa502c500 feat: detect stale config and prompt merge-or-overwrite on save
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 7s
CI / Tests (py3.12) (pull_request) Successful in 8s
Records the file mtime at load time; before write_config() fires, re-checks
it. If it changed (e.g. `claude mcp add`, a second BCC window, or Claude
itself writing ~/.claude.json), StaleDialog prompts with the changed top-level
key names and a masked server-section diff. "Merge & save" applies the user's
in-memory server edits on top of the current on-disk file (preserving external
non-server changes); "Overwrite anyway" proceeds as before.

- bcc_core: config_mtime(), external_change_summary(), _server_sections()
  helper extracted from backup_diff for reuse
- bcc.py: StaleDialog, MainWindow._loaded_mtime tracked through load/save
- tests: 7 new tests (config_mtime, external_change_summary variants, AC merge test)

Closes #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 01:57:32 -04:00
AJ 6ab4789a70 fix: scope backup_diff to server sections and mask secrets
CI / Lint (ruff) (pull_request) Successful in 16s
CI / Tests (py3.10) (pull_request) Successful in 17s
CI / Tests (py3.12) (pull_request) Successful in 14s
The diff preview in RestoreDialog was serializing the full config dict,
exposing secret env values and token args in cleartext on a pasteable surface.

- Add _redact_server_data / _redact_servers_block helpers that apply
  redact_args to args and mask env values for is_secret_key() keys
- Rewrite backup_diff to compare only {mcpServers, _disabledMcpServers}
  sections (sanitized), not the whole file — also avoids double-serializing
  multi-MB ~/.claude.json for a servers-only diff
- Add clarifying comment in _restore_from_backup about why full_config
  is the right base after confirm-discard
- Add test: backup_diff with secret args/env → MASK in output, raw values absent
- Add test: restore_backup restores _disabledMcpServers correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 01:40:24 -04:00
AJ df332a06ba feat: backup restore UI (#3)
CI / Lint (ruff) (pull_request) Successful in 10s
CI / Tests (py3.10) (pull_request) Successful in 10s
CI / Tests (py3.12) (pull_request) Successful in 10s
Add list_backups / backup_label / backup_diff / restore_backup to bcc_core,
RestoreDialog to bcc.py, and a "Restore…" button in the profile top bar.

Restore is selective: only mcpServers and _disabledMcpServers are replaced;
all other keys in the config (history, project state) are preserved verbatim.
Goes through write_config() so a pre-restore backup is always created first.

Closes #3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 01:22:52 -04:00
AJ 231403c1d5 fix: spawn-test stderr persistence and drain-cap deadlock (PR #11 review)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 10s
Two bugs caught in supervisor review:

1. Stderr was silently dropped when the Details panel was closed during a
   crash. _on_spawn_done appended to diag_text only when the panel was
   already open, and refresh_dependency() clobbered that text on the next
   field change anyway.
   Fix: stash the result in self._last_spawn; _full_diag_text() appends
   the stderr section whenever diag text is generated; _on_spawn_done
   auto-opens the panel on non-ok outcomes (same pattern as the existing
   auto_open for missing commands).

2. _drain stopped reading once _STDERR_CAP (4 KB) was reached. A process
   that writes more than 4 KB then blocked on a full pipe buffer, never
   exited, and was misclassified as "ok" instead of "crashed".
   Fix: drain to EOF unconditionally; keep only the first _STDERR_CAP bytes.
   Regression test: 64 KB stderr + exit(3) → outcome "crashed".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 01:05:34 -04:00
AJ e98cb7abd7 feat: stdio server spawn-test (issue #2)
CI / Lint (ruff) (pull_request) Successful in 8s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 7s
Add spawn_test() to bcc_core — spawns a stdio server for up to 3 s,
captures stderr, and reports ok/exited/crashed/not_found. Key design
decisions driven by real MCP server behaviour:

- stdin=PIPE (never written): servers block on JSON-RPC input and stay
  alive, so "still running after timeout" reliably signals a healthy
  start. stdin=DEVNULL would send EOF, causing well-behaved servers to
  exit 0 and be misclassified as "exited".
- Command resolved via shutil.which(augmented_path()) before Popen so
  subprocess PATH resolution is unambiguous across platforms.
- start_new_session=True on POSIX + os.killpg on timeout: kills the
  whole process group, not just the launcher (npx, uvx), which would
  otherwise orphan the actual node/python grandchild process.
- stdout=DEVNULL: draining a PIPE we don't read would deadlock at ~64 KB.
- stderr drained in a daemon thread, capped at 4 KB.

GUI: SpawnTester(QThread) wraps spawn_test; "Test launch" button in
ServerEditor dep row (stdio only, visible when command resolves ok/warn).
Result colours match the existing dep-status palette (green/amber/red).
Stderr appended to the diagnostics panel if it is open.

7 new unit tests cover all outcomes and the stdin-open regression guard.
Ran python bcc.py locally: button appears for stdio servers whose command
resolves, is hidden for remote servers and missing-command servers.

Closes #2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 00:57:46 -04:00
AJ 6dee318976 feat: mask secret values in the UI and redact them from diagnostics
- Env/header values whose key looks secret (TOKEN, API_KEY, PASSWORD,
  AUTH, ...) render as •••••••• via a display-only delegate; a
  'Show secrets' toggle reveals them. Underlying data, editing, and
  save are untouched.
- The Add dialog switches the value field to password echo when the
  key name looks secret.
- Copy-diagnostics now redacts secrets from args (--token <v>,
  --api-key=<v>, and well-known token prefixes like ghp_/sk-/xoxb-),
  since those reports get pasted into public bug reports. Env and
  header values were already omitted from diagnostics.
2026-07-02 00:31:34 -04:00
AJ b16c0fd526 fix: point Claude Code profile at ~/.claude.json, not settings.json
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10) (push) Successful in 7s
CI / Tests (py3.12) (push) Successful in 7s
Claude Code stores user-scope MCP servers in ~/.claude.json (what
'claude mcp add' writes); ~/.claude/settings.json is for permissions
and hooks and rejects an mcpServers key with a schema error, so BCC
was reading (and writing) servers where Claude Code never looks.

If servers are found parked in settings.json, that file is still
listed as 'Claude Code (legacy settings.json)' so they can be copied
into the real config via Copy to. Docs updated; verified against
docs.claude.com/en/docs/claude-code/settings.
2026-07-02 00:27:01 -04:00
AJ e11886bde9 feat: clearer Arguments editor with typed-together detection and one-click fix
- Label now explains the model with an example: a flag and its value go
  on separate lines. Placeholder shows the common uv pattern.
- split_suspicious_args() flags lines that contain whitespace plus a
  dash-prefixed token ('--directory /path') — legit single args with
  spaces ('My Documents') are never touched. Quotes are respected.
- The editor shows a warning under the args box with a 'Fix: split onto
  separate lines' button; the fix goes through the undo stack.
2026-07-02 00:13:06 -04:00
AJ 77f469c1dd feat: offer guided repair when loading a broken config file
If a config file on disk fails strict JSON parsing, BCC now runs it
through the same repair pipeline as pasted snippets and shows a dialog
listing the parse error, each fix it would apply, and a preview of the
resulting file. The user chooses: Repair & load (marks the profile
dirty; the file is only rewritten on Save, after the broken original
is backed up) or Cancel. Unsalvageable files keep the old error path.

repair_config_file() in bcc_core never writes to disk itself.
2026-07-02 00:10:27 -04:00
AJ 6205c85b61 feat: lenient JSON repair for pasted MCP snippets
CI / Lint (ruff) (push) Successful in 10s
CI / Tests (py3.10) (push) Successful in 23s
CI / Tests (py3.12) (push) Successful in 10s
Pasted config snippets no longer have to be valid JSON. repair_json_text()
auto-fixes markdown fences, surrounding prose, // /* */ # comments,
trailing and missing commas, smart quotes, single quotes, unquoted keys,
Python/JS literals, and unclosed braces. parse_pasted_json_verbose()
reports every repair applied; the paste dialog now parses as you type
and previews exactly what will be added and what was fixed.

Also includes ruff lint fixes and formatting across bcc.py/bcc_core.py.
2026-07-01 23:43:39 -04:00
AJ Avezzano 2e33850fe3 Fix PATH check, add undo, and popup dialog for env/header vars
PATH check:
- Add system_path() that reads /etc/paths + /etc/paths.d/* + well-known
  package manager dirs (/opt/homebrew/bin, /usr/local/bin, /opt/local/bin)
  so the ok/warn distinction is consistent whether BCC runs from terminal
  or as a .app — /opt/homebrew/bin/npx now correctly shows as ok
- augmented_path() now uses system_path() as its base and also includes
  the current process PATH (handles CI / container setups)
- diagnostics_text() uses system_path() to mark user-specific dirs with +

Undo:
- _push_undo() / _undo() snapshot-based undo with 50-entry stack
- Undo button in action bar + Ctrl+Z shortcut
- Hooked into: add server, duplicate, delete, paste JSON, enable/disable
  toggle, drag-drop import, and env/header row add/remove (via
  before_change callback chain through ServerEditor → KeyValueTable)
- Undo stack resets on profile load

Variable/header entry:
- KeyValueTable.+ Add now opens a popup dialog with labeled fields
  (key name + OK disabled until non-empty) instead of inserting an
  invisible blank row — applies consistently to both env and headers tables
- Pressing Enter in key field moves to value; Enter in value submits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 15:34:18 -04:00
AJ Avezzano dd7557ea62 Add cross-platform packaging, icons, and Claude Code config discovery
Build & Release / Build (Linux) (push) Failing after 2m56s
Build & Release / Build (macOS) (push) Successful in 4m24s
Build & Release / Build (Windows) (push) Has been cancelled
Build & Release / Publish Release (push) Has been cancelled
- Auto-discovers Claude Code (~/.claude/settings.json) alongside Claude Desktop
- Generated icons/app.icns (macOS) and icons/app.ico (Windows) from rounded PNGs
- bcc.spec: PyInstaller spec for all platforms (.app on macOS, onefile on Windows/Linux)
- .github/workflows/release.yml: builds and publishes GitHub Release on version tags
- scripts/build_icons.py: regenerates icon files from source PNGs
- requirements-dev.txt: adds pyinstaller and pillow for building
- CLAUDE.md: initial repo guidance for Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 15:01:30 -04:00
AJ Avezzano 780d4c3a9c Initial commit: Better Claude Config 2026-06-29 14:12:50 -04:00