feat(cli): installer subcommands, real config validation, status timeout#223
feat(cli): installer subcommands, real config validation, status timeout#223
Conversation
…tus timeout - Wire install/repair/uninstall/inspect commands into ArgumentParser CLI, delegating to InstallerEngine and PrivilegeBroker (restores P1 Codex item) - Replace `config check` file-presence check with real kanata --check validation via ConfigurationService.validateConfiguration() - Add --timeout flag to `keypath status` (default 30s) to prevent hangs from SMAppService synchronous IPC under launchd load - Remove @mainactor from applyConfiguration(), isolate only the accesses that need it (ConfigurationService init, PreferencesService port read) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Code Review - PR 223Overall this is a solid, well-scoped PR. All three headline improvements are correct architectural moves. A few issues worth addressing before merge: Bug - stdout pollution before JSON output (Medium) In InstallCommand.swift the preamble messages (e.g. 'Starting installation...') go to stdout even when --json is passed. Same issue in Repair and Uninstall. Scripts piping output to jq will see garbled content mixing the preamble string with the JSON object. Fix: route preamble through printErr() so it goes to stderr, or gate it with 'if !json'. Bug - Force unwrap in Timeout.swift (Low) The two-task setup makes nil practically impossible today, but force-unwraps are fragile against future refactors. Prefer: Issue - UInt64 overflow in sleep duration A very large --timeout value overflows. Consider capping the input or using the Duration-based API on macOS 13+: Design - planStatus as raw String in CLIInspectResult planStatus is 'ready' or 'blocked' -- stringly-typed for JSON consumers. A Codable enum would be more robust; at minimum add a comment near the struct documenting the valid values. Quality - printInstallerReport is a module-level free function No access modifier makes it internal across all of KeyPathCLI. Mark it private or scope it inside KeyPathTool to prevent future naming conflicts. Minor - Two InstallerEngine instances in runRepair() A first engine is created for inspection, then a second InstallerEngine() is created for the full repair path. Reuse the first instance by hoisting it before the branch. Tests - no new coverage for the 4 new commands All 307 existing tests pass, which is great. But the fast-repair path decision logic and inspect-plan serialisation have no test coverage. Not a merge blocker, but worth a follow-up issue. Positives
|
…smart drawer toggle - Add Shift as a tab in the Tap/Hold/Combo behavior picker (now Tap/Hold/Shift/Combo) - Move multi-tap config into the input condition dropdown (Single/Double/Triple Tap) instead of a separate slide-over panel — output keycap shows the selected tap count's action - All behavior labels (Hold, Shift, Combo, 2× Tap) now float above the input keycap since they all describe input triggers, not output behaviors - Conditionally show drawer toggle in header: hidden when layout has its own drawer buttons (Touch ID, Layer keys), shown with sidebar icon as fallback for layouts without them (ansi-60, ansi-65, hhkb, sofle) - Remove hide/close button from overlay header (use ⌘⌥K or Apple menu) - Simplify MultiTapSlideOverView with compact inline rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove redundant KanataConfigGenerator from mapper/recording save paths; use single regenerateConfigFromCollections pipeline that correctly handles forks, tap-hold, tap-dance, chords, and macros - Fix Shift+key custom output not taking effect (fork directive now applied) - Add shift behavior pre-population with system defaults (1→!, ;→:, etc.) - Show custom shift labels on overlay keyboard keycaps - Regenerate behavior icons with clean chroma-key backgrounds - Add shift behavior slot icons - Move behavior animations from output to input keycap - Make "Saved" status message transient (auto-dismiss after 2s) - Hide X button on shift slot when showing system default - Add glow effect to input keycap labels when drawer fades keyboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove old KeyPathCLI.swift and KeyPathCLIEntrypoint.swift (moved to KeyPathCLI target) - Remove CLI test file (moved to KeyPathCLI target tests) - Remove CLI import from Main.swift - Update help-theme.css and neovim-terminal help article - Add neovim-terminal header image - Minor Karabiner import sheet tweaks - Add karabiner converter feature matrix doc - Update KEYPATH_GUIDE and refactoring plan docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Code Review Overall this is solid work. The CLI subcommands, real config validation, and status timeout address real pain points. The UI refactoring (Shift slot, multi-tap dropdown, glow effects) is coherent. A few things worth fixing before merge. BUGS
group.next() can return nil if all tasks complete without producing a value. This crashes. Fix: guard let result = try await group.next() else { throw TimeoutError() }
In Install, Repair, and Uninstall, progress strings land on stdout before the JSON output runs, breaking automated JSON consumers. Fix: suppress in JSON mode or redirect via printErr(). ARCHITECTURE AND CLAUDE.md VIOLATIONS
After coordinator.restartService() succeeds, the code calls engine.inspectSystem() to verify health. But inspectSystem() calls SMAppService.status, which CLAUDE.md documents as synchronous IPC taking 10-30s under launchd load -- the exact problem that motivated the new --timeout flag on keypath status. The fast-repair path can therefore be just as slow. Consider verifying liveness via the TCP health check only (Kanata responding on TCP = service is live), or document that this path has the same latency caveat.
One for fast-path inspection, one for full repair. If system state changes between calls, the second engine operates on stale context. Reuse the same instance, or document the intentional race window.
The sentinel 0xFFFF in keys.contains { keyCode == 0xFFFF } has no comment or named constant. Future maintainers will have no idea what it represents. MINOR ISSUES
The switch only handles case 2 and case 3; default: nil means a 4x tap condition shows Everywhere instead of 4x Tap. Since tapCounts = [2, 3, 4], add case 4.
The same 3-line JSONEncoder setup (prettyPrinted + sortedKeys) is copy-pasted into all 4 new commands and Config.Check. Extract to a shared static helper.
applyConfiguration() had @mainactor removed in this PR (correct). validateConfig() was added with it. If neither ConfigurationService.current() nor validateConfiguration() require the main actor, the annotation adds unnecessary actor hops in a CLI context.
CLIInstallerReport is public but both initializers are internal. Either make them public or demote the struct to internal. WHAT IS WORKING WELL
|
|
PR Review test placeholder |
|
PR Review: feat(cli): installer subcommands, real config validation, status timeout Overall this is a solid, well-motivated PR. The migration from KeyPathCLI/KeyPathCLIEntrypoint to proper ArgumentParser subcommands is the right architectural move. The @mainactor narrowing in applyConfiguration() and the --timeout guard on status are both targeted, correct fixes. Issues worth addressing before merge 1. Negative/zero --timeout will trap at runtime Timeout.swift:12 does UInt64(seconds) * 1_000_000_000. UInt64(-1) traps at runtime; UInt64(0) races the work task. No validate() on StatusCommand rejects non-positive values. Suggest adding a validate() method that throws ValidationError when timeout < 1. 2. Test coverage regression: 144 lines deleted, 0 added for new commands KeyPathCLITests.swift is removed because KeyPathCLI is gone -- fair. But Install, Repair, Uninstall, Inspect, and the new validateConfig() path have no unit tests. The fast-repair path in runRepair() is particularly tricky (two engine instances, conditional early return) and would benefit from CLIFacadeTests. All 307 tests pass covers the old surface area, not the new one. Notable design feedback 3. runRepair() fast path calls inspectSystem() -- the slow IPC call you are trying to avoid The motivation for --timeout on status is that SMAppService.status can block 10-30s under load (per CLAUDE.md). The fast-repair path in CLIFacade.runRepair() calls engine.inspectSystem() unconditionally after restartService() returns true -- same latency exposure. Consider wrapping it with the timeout helper or trusting the restartService() bool and skipping the post-check. 4. runRepair() creates two InstallerEngine() instances Fast path creates one engine for inspection; if it falls through, a second is created for full repair. A single let engine = InstallerEngine() before the branch cleans this up. 5. printInstallerReport is a free function at module scope InstallCommand.swift line 131 is globally visible and will conflict with anything else in the module defining printInstallerReport. Should be private func or a static func on a private helper type. 6. CLIInspectResult omits timestamp CLIStatusResult has a timestamp: Date field. CLIInspectResult does not, even though engine.inspectSystem() returns a SystemContext with a timestamp. Scripts correlating inspect output with logs will have no time anchor. 7. withThrowingTimeout force-unwrap group.next()! is safe given the structure (two tasks always added), but non-obvious. A guard-let with explicit throw is clearer. Things done well
Minor nits
|
…version (#224) * feat(cli): add --apply flag, layer command, completions, and dynamic version - Add --apply flag to remap, rules enable, and rules disable commands to regenerate config and reload Kanata in one step - Add `keypath layer` command with list and switch subcommands via TCP - Add `keypath completions` command for zsh, bash, and fish shell completions - Read CLI version from KeyPath.app bundle instead of hardcoded value Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: address PR #224 review feedback - DRY apply logic into shared applyConfigurationOrHint helper - Add ~/Applications fallback for CLI version detection - Improve TCP error handling in layer list command - Drop misleading index from layer list output - Add comment about ArgumentParser built-in completion script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
install,repair,uninstall, andinspectsubcommands into the ArgumentParser CLI, delegating toInstallerEngineandPrivilegeBroker(addresses Codex P1 regression)config checkfile-presence check with realkanata --checkvalidation viaConfigurationService.validateConfiguration()--timeoutflag tokeypath status(default 30s) to prevent hangs fromSMAppServicesynchronous IPC under launchd load@MainActorfromapplyConfiguration(), isolating only the accesses that need itCommands Added
keypath install [--json]keypath repair [--json]keypath uninstall [--delete-config] [--json]keypath inspect [--json]Commands Updated
keypath config checkkanata --checkinstead of file presence check; adds--jsonkeypath status--timeout <seconds>flag (default 30)Test plan
swift build --product keypathcompiles cleanlyswift build --product KeyPathcompiles cleanlyswift test— all 307 tests pass, no regressions🤖 Generated with Claude Code