Skip to content

437: opskit go — TUI project launcher with opencode integration - #438

Merged
growlf merged 10 commits into
mainfrom
437-opskit-go-tui-project-launcher-with-opencode-integ
Oct 13, 2026
Merged

growlf merged 10 commits into
mainfrom
437-opskit-go-tui-project-launcher-with-opencode-integ

Conversation

@growlf

@growlf growlf commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Summary

Adds and commands to launch opencode in any OpsKit-enabled project via a fuzzy-search TUI, with per-project preferences stored in ~/.opskit/, secrets passthru interface, and skill symlink management.

Changes

New files

  • opskit/_cache.py — shared cache module with atomic read/write
  • bin/opskit-go.py — TUI launcher (fzf select → review/accept → launch)
  • bin/opskit-web.py — opencode web --mdns wrapper
  • bin/opskit-skills.py — skill symlink management (setup/verify/cleanup)
  • bin/opskit-secrets.py — secrets engine router
  • bin/opskit-projects.py — project cache management (rescan/add/list/config)

Modified files

  • bin/opskit — add go/web/skills/projects subcommands + shell completions
  • install.sh — add fzf to PKG_CLI

Tests

  • tests/test_cache.py — cache module tests
  • tests/test_projects.py — project cache tests
  • tests/test_secrets.py — secrets script tests
  • tests/test_skills.py — skills script tests
  • tests/test_tui_matching.py — TUI matching logic tests
  • tests/test_opskit_*.py — definition-of-done wrapper tests

Commands

opskit go [<project>]

Launch opencode in an OpsKit-enabled project.

  • TUI: fuzzy search → review/accept → launch
  • Direct: opskit go Systate (exact match) or opskit go opskit (fuzzy match)

opskit web

Launch opencode web --mdns with full opskit environment.

opskit skills setup|verify|cleanup

Manage skill symlinks in ~/.config/opencode/skills/ and ~/.agents/skills/opskit/.

opskit projects rescan|add|list|config

Manage project cache for project discovery.

Testing

  • make test: 1645 passed (1 pre-existing failure in test_mail_auth_check.py)
  • Functional tests: all scripts tested with --help, rescan, verify, cleanup, launch

Design Decisions

  • fzf as hard dependency (installed by install.sh, checked by opskit check)
  • Cache-first project discovery (scan on-demand only)
  • Shared cache module (_cache.py) for atomic operations
  • Secrets passthru is extensible (bitwarden → bwunlock.sh, env → env var)
  • Symlink conflicts are reported, --force to overwrite
  • ~/.opskit/ created with 0700 permissions

Closes #437

@growlf growlf linked an issue Oct 13, 2026 that may be closed by this pull request

@growlf growlf left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review — request changes

Reviewed the full diff at a6081e0 in a worktree and re-ran the suite: 1645 passed, 1 skipped, 1 failed — the failure is the pre-existing test_mail_auth_check.py one, unrelated to this PR. The scoped new tests pass. But there are three blocking defects, all reproduced, plus a pile of test/quality problems.

Blocking

  1. opskit projects config set <path> does not work at all. The bin/opskit dispatcher for projects forwards only path and drops action, so opskit-projects.py config receives the path as its action positional and argparse rejects it:

    $ opskit projects config set /tmp/newdir
    opskit-projects.py config: error: argument action: invalid choice: '/tmp/newdir' (choose from show, set)
    

    bin/opskit:1914-1919. Even with the argv fixed, set is never passed through.

  2. opskit skills cleanup deletes symlinks it does not own. cmd_cleanup unlinks anything at <dir>/<skill-name> that is_symlink(), with no check that it points into the opskit skills tree. Reproduced: a ~/.config/opencode/skills/backup -> /etc/hostname symlink is removed. This contradicts the plan's own claim ("removes only opskit-managed symlinks (identified by target path)" and "Symlink targets validated to be within ~/Projects/opskit/"). bin/opskit-skills.py:190-217.

  3. cache_is_stale() never checks projects_dir mtime — the whole branch is dead code. Both sides are pass; now/dir_mtime_dt are computed and discarded. The plan promises "cache is stale if the projects_dir mtime has changed since the cache was written", and opskit go never calls cache_is_stale() at all (it just errors "No projects cached"). So the documented scan-when-stale behaviour is not implemented. opskit/_cache.py:94-135, bin/opskit-go.py:358-361.

Should fix

  1. Secrets passthru bypasses the vault session resolver. resolve_bitwarden_session() runs bwunlock.sh --check (which reports the session source) but then ignores it and reads a hardcoded ~/.cache/opskit/bw-session. That reintroduces a second definition of the session path, bypasses the env-wins rule and the single-source-of-truth in bin/bw_session.py that #155 was about, and means an exported BW_SESSION is never picked up. It also never actually unlocks — a locked vault silently yields an empty session. bin/opskit-secrets.py:79-104.
  2. Duplicate get_projects_dir() defined back-to-back in bin/opskit-go.py:50 and :65 (verbatim copy).
  3. Duplicate test files: tests/test_secrets.py is byte-identical to tests/test_opskit_secrets.py (same md5); test_skills.py/test_opskit_skills.py and test_projects.py/test_opskit_projects.py are near-duplicates. Pick one convention.
  4. Tests don't test the implementation. tests/test_tui_matching.py copy-pastes fuzzy_match_projects instead of importing it from opskit-go.py, so test_opskit_go.py tests the copy; test_cache.py asserts the tautology result is False or result is True; the test_opskit_* wrappers only check --help and that the file compiles. Nothing covers skills setup/verify/cleanup, secrets routing, or rescan.
  5. Dead code / unused imports: opskit/_cache.py (shutil, time), opskit-go.py (json, datetime/timezone, CACHE_DIR, read_last_used, get_sort_key), opskit-projects.py (cache_is_stale, _os), opskit-secrets.py (json).
  6. Prefix-string containment check in cmd_add (str(p).startswith(str(projects_dir))) matches /home/u/Projects2 for /home/u/Projects; use Path.is_relative_to().
  7. Dispatcher swallows failures — bin/opskit:1914-1919 and the go/web/skills branches sys.exit(0) unconditionally, hiding subprocess return codes.
  8. plans/437-...md is a tracked, public file carrying operator-specific instructions ("After merging, restore stashed changes from main: git stash pop") and a real path (/home/netyeti/Projects/Phoenix). Strip that before it's published.

The feature is well-shaped and the whole-suite result is clean, but items 1–3 are broken behaviour shipped with tests that pass anyway — that's the worst combination. Please fix those three, consolidate the tests, and re-push.

Fixes from the PR #438 review:

- bin/opskit: intercept go/web/skills/projects before argparse and forward
  argv verbatim, so the sub-script owns its argument surface. Previously a
  hand-translated parser silently dropped args (and `--help` was answered by
  a dispatcher stub).
- bin/opskit-go.py: rescan when the discovery cache is missing or stale
  instead of dead-ending on "No projects cached"; drop the duplicated
  get_projects_dir; trim unused imports.
- bin/opskit-projects.py: record projects_dir mtime at scan time; share
  config read/write via opskit._cache (single definition).
- opskit/_cache.py: real mtime staleness check (the old branch was dead code
  with both sides `pass`); add read_config/write_config/get_projects_dir.
- bin/opskit-skills.py: cleanup removes only opskit-managed symlinks
  (_is_opskit_link), preserving a user symlink that merely shares a name.
- bin/opskit-secrets.py: delegate session resolution to bin/bw_session.py
  (env-wins, one definition) instead of hardcoding the session path.
- tests: drop byte-duplicate feature-named files, exercise the shipped
  functions instead of copies, replace a tautological assertion, and add
  behavior/regression cover (dispatcher passthrough, skills cleanup safety,
  cache mtime staleness, secrets routing, projects rescan/config).
@growlf

growlf commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

Review findings addressed in 752c4e4.

Dispatcher (bin/opskit) — go/web/skills/projects are now intercepted before argparse and forward argv verbatim, so the sub-script owns its argument surface. The hand-translated parser that dropped args (and answered --help with a stub) is gone. The subparsers remain only so opskit --help lists them.

Cache staleness — cache_is_stale had a dead mtime branch (both sides pass). It now compares the projects_dir mtime recorded at scan time against the current one. opskit-go.py rescans when the cache is missing or stale instead of dead-ending on "No projects cached".

Vault session — opskit-secrets.py delegates to bin/bw_session.py --token (one definition of env-wins/path/permission) instead of hardcoding ~/.cache/opskit/bw-session.

Skills cleanup — cmd_cleanup removes only opskit-managed symlinks; a user symlink that merely shares a skill's name is preserved and reported as skipped.

Dedup/hygiene — shared read_config/write_config/get_projects_dir moved into opskit/_cache.py; removed the duplicated get_projects_dir and unused imports; Path.is_relative_to for the projects-dir containment check; plan-file hygiene.

Tests — dropped the byte-duplicate feature-named files; test_tui_matching now loads the shipped opskit-go.py instead of a copy; replaced the tautological assertion with real mtime tests; added behavior/regression cover (dispatcher passthrough, skills-cleanup safety, secrets routing, projects rescan/config, cache staleness).

Full suite: 1655 passed, 1 skipped. The one failure (test_mail_auth_check.py::test_the_startsession_nudge...) is pre-existing and unrelated — no touched file overlaps it.

@growlf
growlf merged commit 815720c into main Oct 13, 2026
9 checks passed
@growlf
growlf deleted the 437-opskit-go-tui-project-launcher-with-opencode-integ branch October 13, 2026 03:59
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.

opskit go: TUI project launcher with opencode integration

1 participant