kioku-cli 0.6.0.0 → 0.7.0.0
raw patch · 7 files changed
+230/−115 lines, 7 filesdep ~keirodep ~kioku-apidep ~kioku-cliPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: keiro, kioku-api, kioku-cli, kioku-core, kioku-migrations, optparse-applicative
API changes (from Hackage documentation)
+ Kioku.Cli: cliParserInfo :: ParserInfo Command
+ Kioku.Cli: cliParserPrefs :: ParserPrefs
Files
- CHANGELOG.md +10/−0
- kioku-cli.cabal +75/−63
- src/Kioku/Cli.hs +18/−10
- src/Kioku/Cli/Commands/Distill.hs +26/−19
- src/Kioku/Cli/Commands/Recall.hs +27/−20
- src/Kioku/Cli/Commands/Worker.hs +4/−1
- test/Kioku/Cli/ParserSpec.hs +70/−2
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Changelog +## 0.7.0.0 — 2026-09-18++### Changed++- Requires the Kioku 0.7 cohort and optparse-applicative `>=0.19 && <0.20`.+- Help renders at a stable 100-column width and groups recall targets, query/output controls, AI+ configuration, and worker execution modes under semantic headings. Command names, option+ spellings, defaults, and errors are unchanged.+- Bash, zsh, and fish completion-script requests are covered as public parser behavior.+ ## 0.6.0.0 — 2026-09-08 ### Breaking Changes
kioku-cli.cabal view
@@ -1,35 +1,40 @@-cabal-version: 3.0-name: kioku-cli-version: 0.6.0.0-synopsis: kioku command-line interface+cabal-version: 3.0+name: kioku-cli+version: 0.7.0.0+synopsis: kioku command-line interface description: Command-line entry point for kioku demos and operational commands. -license: BSD-3-Clause-license-file: LICENSE-author: Nadeem Bitar-maintainer: nadeem@gmail.com-copyright: 2026 Nadeem Bitar-category: Data-build-type: Simple-tested-with: GHC >=9.12 && <9.13-homepage: https://github.com/shinzui/kioku-bug-reports: https://github.com/shinzui/kioku/issues+license: BSD-3-Clause+license-file: LICENSE+author: Nadeem Bitar+maintainer: nadeem@gmail.com+copyright: 2026 Nadeem Bitar+category: Data+build-type: Simple+tested-with: ghc >=9.12 && <9.13+homepage: https://github.com/shinzui/kioku+bug-reports: https://github.com/shinzui/kioku/issues extra-doc-files: CHANGELOG.md source-repository head- type: git+ type: git location: https://github.com/shinzui/kioku.git- subdir: kioku-cli+ subdir: kioku-cli common warnings ghc-options:- -Wall -Wcompat -Widentities -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+ -Wall+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wpartial-fields+ -Wredundant-constraints -Werror=incomplete-patterns common shared- default-language: GHC2024+ default-language: GHC2024 default-extensions: BlockArguments DeriveAnyClass@@ -42,8 +47,8 @@ TemplateHaskell library- import: warnings, shared- hs-source-dirs: src+ import: warnings, shared+ hs-source-dirs: src exposed-modules: Kioku.Cli Kioku.Cli.AIConfig@@ -72,61 +77,68 @@ -- mori://MMZK1526/mmzk-typeid/upstream-issues/mmzk-typeid-kindid-ghc-9-12-4-profiling-coercionkind-panic ghc-prof-options: -fno-opt-coercion build-depends:- , async >=2.2 && <2.3- , baikai ^>=0.7.0.0- , base >=4.21 && <5- , containers >=0.6 && <0.8- , directory >=1.3 && <1.4- , effectful >=2.5 && <2.7- , keiro ^>=0.16.0.0- , kioku-api ^>=0.6.0.0- , kioku-core ^>=0.6.0.0- , kiroku-store ^>=0.8.0.0- , optparse-applicative >=0.18 && <0.20- , text >=2.1 && <2.2- , time >=1.12 && <1.15- , uuid >=1.3 && <1.4+ async >=2.2 && <2.3,+ baikai ^>=0.7.0.0,+ base >=4.21 && <5,+ containers >=0.6 && <0.8,+ directory >=1.3 && <1.4,+ effectful >=2.5 && <2.7,+ keiro ^>=0.17.0.0,+ kioku-api ^>=0.7.0.0,+ kioku-core ^>=0.7.0.0,+ kiroku-store ^>=0.8.0.0,+ optparse-applicative >=0.19 && <0.20,+ text >=2.1 && <2.2,+ time >=1.12 && <1.15,+ uuid >=1.3 && <1.4, executable kioku- import: warnings, shared- main-is: Main.hs+ import: warnings, shared+ main-is: Main.hs hs-source-dirs: app- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N+ build-depends:- , base >=4.21 && <5- , kioku-cli ^>=0.6.0.0+ base >=4.21 && <5,+ kioku-cli ^>=0.7.0.0, test-suite kioku-cli-test- import: warnings, shared- type: exitcode-stdio-1.0- main-is: Main.hs- hs-source-dirs: test+ import: warnings, shared+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: test other-modules: Kioku.Cli.ParserSpec Kioku.Cli.RecallEndToEndSpec - ghc-options: -threaded -rtsopts -with-rtsopts=-N+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N -- Kioku.Cli.RecallEndToEndSpec runs the real binary as a subprocess: stdout, stderr and the -- environment are process-wide, and tasty runs cases concurrently, so redirecting them in -- process would race the rest of the suite. build-tool-depends: kioku-cli:kioku build-depends:- , aeson >=2.2 && <2.3- , base >=4.21 && <5- , effectful >=2.5 && <2.7- , filepath >=1.4 && <1.6- , keiro ^>=0.16.0.0- , kioku-api ^>=0.6.0.0- , kioku-cli ^>=0.6.0.0- , kioku-core ^>=0.6.0.0- , kioku-migrations:test-support ^>=0.6.0.0- , kiroku-store ^>=0.8.0.0- , optparse-applicative >=0.18- , process >=1.6 && <1.7- , tasty >=1.5- , tasty-hunit >=0.10- , temporary >=1.3- , text >=2.1- , time >=1.12 && <1.15- , uuid >=1.3 && <1.4+ aeson >=2.2 && <2.3,+ base >=4.21 && <5,+ effectful >=2.5 && <2.7,+ filepath >=1.4 && <1.6,+ keiro ^>=0.17.0.0,+ kioku-api ^>=0.7.0.0,+ kioku-cli ^>=0.7.0.0,+ kioku-core ^>=0.7.0.0,+ kioku-migrations:test-support ^>=0.7.0.0,+ kiroku-store ^>=0.8.0.0,+ optparse-applicative >=0.19 && <0.20,+ process >=1.6 && <1.7,+ tasty >=1.5,+ tasty-hunit >=0.10,+ temporary >=1.3,+ text >=2.1,+ time >=1.12 && <1.15,+ uuid >=1.3 && <1.4,
src/Kioku/Cli.hs view
@@ -1,5 +1,7 @@ module Kioku.Cli- ( main,+ ( cliParserInfo,+ cliParserPrefs,+ main, ) where @@ -16,15 +18,21 @@ data Command = Artifacts ArtifactsOptions | Demo DemoOptions | DemoSession DemoSessionOptions | Distill DistillOptions | Persona PersonaOptions | Recall RecallOptions | Scenes ScenesOptions | Worker WorkerOptions main :: IO ()-main = run =<< execParser opts- where- opts =- info- (commandParser <**> helper)- ( fullDesc- <> progDesc "kioku reusable agent memory tools"- <> header "kioku"- )+main = run =<< customExecParser cliParserPrefs cliParserInfo++-- | A fixed width makes help output stable in terminals, tests, and generated+-- documentation instead of inheriting the invoking process's console width.+cliParserPrefs :: ParserPrefs+cliParserPrefs = prefs (columns 100)++cliParserInfo :: ParserInfo Command+cliParserInfo =+ info+ (commandParser <**> helper)+ ( fullDesc+ <> progDesc "kioku reusable agent memory tools"+ <> header "kioku"+ ) commandParser :: Parser Command commandParser =
src/Kioku/Cli/Commands/Distill.hs view
@@ -49,25 +49,32 @@ <$> argument (eitherReader parseSessionId) (metavar "SESSION_ID")- <*> option- (eitherReader parseCandidateSource)- ( long "candidates"- <> metavar "scan|recall"- <> value CandidateScan- <> help "Candidate lookup source"- )- <*> option- (boundedIntReader "LIMIT" 1 50)- ( long "limit"- <> metavar "N"- <> value 5- <> help "Maximum merge candidates per extracted atom (1-50)"- )- <*> switch- ( long "force"- <> help "Re-run even when the session has no turns newer than the last successful pass"- )- <*> aiConfigOption+ <*> parserOptionGroup "Candidate options" candidateSourceParser+ <*> parserOptionGroup "Candidate options" candidateLimitParser+ <*> parserOptionGroup "Execution options" forceParser+ <*> parserOptionGroup "AI options" aiConfigOption+ where+ candidateSourceParser =+ option+ (eitherReader parseCandidateSource)+ ( long "candidates"+ <> metavar "scan|recall"+ <> value CandidateScan+ <> help "Candidate lookup source"+ )+ candidateLimitParser =+ option+ (boundedIntReader "LIMIT" 1 50)+ ( long "limit"+ <> metavar "N"+ <> value 5+ <> help "Maximum merge candidates per extracted atom (1-50)"+ )+ forceParser =+ switch+ ( long "force"+ <> help "Re-run even when the session has no turns newer than the last successful pass"+ ) runDistill :: DistillOptions -> IO () runDistill opts = do
src/Kioku/Cli/Commands/Recall.hs view
@@ -72,26 +72,33 @@ recallOptionsParser = RecallOptions <$> (Text.pack <$> argument str (metavar "QUERY"))- <*> recallTargetParser- <*> option- (eitherReader (first Text.unpack . parseRecallStrategy . Text.pack))- ( long "strategy"- <> metavar strategyMetavar- <> value Hybrid- <> help "Recall strategy"- )- <*> option- (boundedIntReader "LIMIT" 1 100)- ( long "limit"- <> metavar "N"- <> value 8- <> help "Maximum hits to return (1-100)"- )- <*> switch- ( long "show-scores"- <> help "Print fused scores and component ranks"- )- <*> aiConfigOption+ <*> parserOptionGroup "Recall target" recallTargetParser+ <*> parserOptionGroup "Query options" strategyParser+ <*> parserOptionGroup "Query options" limitParser+ <*> parserOptionGroup "Output options" showScoresParser+ <*> parserOptionGroup "AI options" aiConfigOption+ where+ strategyParser =+ option+ (eitherReader (first Text.unpack . parseRecallStrategy . Text.pack))+ ( long "strategy"+ <> metavar strategyMetavar+ <> value Hybrid+ <> help "Recall strategy"+ )+ limitParser =+ option+ (boundedIntReader "LIMIT" 1 100)+ ( long "limit"+ <> metavar "N"+ <> value 8+ <> help "Maximum hits to return (1-100)"+ )+ showScoresParser =+ switch+ ( long "show-scores"+ <> help "Print fused scores and component ranks"+ ) -- | Exactly one of the three target flags, and never two. --
src/Kioku/Cli/Commands/Worker.hs view
@@ -61,7 +61,10 @@ deriving stock (Eq, Show) workerOptionsParser :: Parser WorkerOptions-workerOptionsParser = (\config mode -> maybe mode (`WorkerConfigured` mode) config) <$> aiConfigOption <*> workerModeParser+workerOptionsParser =+ (\config mode -> maybe mode (`WorkerConfigured` mode) config)+ <$> parserOptionGroup "AI options" aiConfigOption+ <*> parserOptionGroup "Execution mode" workerModeParser workerModeParser :: Parser WorkerOptions workerModeParser =
test/Kioku/Cli/ParserSpec.hs view
@@ -10,6 +10,7 @@ import Keiro.Timer (TimerId (..)) import Kioku.Api.Access (mkMemorySpaceId) import Kioku.Api.Scope (MemoryScope (..), Namespace (..), ScopeKind (..))+import Kioku.Cli (cliParserInfo, cliParserPrefs) import Kioku.Cli.Commands.Demo (DemoOptions (..), demoOptionsParser, demoScope) import Kioku.Cli.Commands.DemoSession (DemoSessionOptions (..), demoSessionOptionsParser) import Kioku.Cli.Commands.Distill (DistillOptions (..), distillOptionsParser)@@ -27,6 +28,7 @@ recallStrategyText, ) import Options.Applicative+import System.Exit (ExitCode (..)) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertBool, testCase, (@?=)) @@ -41,7 +43,8 @@ limitTests, demoGuardTests, redactionTests,- workerModeTests+ workerModeTests,+ cliContractTests ] -- | Run a parser against an argument list, rendering a failure the way the real CLI would.@@ -157,7 +160,7 @@ Left err -> do assertBool ("failure should say what is missing: " <> err) ("Missing:" `isInfixOf` err) mapM_- (\flag -> assertBool ("failure should list " <> flag <> ": " <> err) (flag `isInfixOf` err))+ (\flagName -> assertBool ("failure should list " <> flagName <> ": " <> err) (flagName `isInfixOf` err)) ["--scope", "--global-bucket", "--namespace-wide"], testCase "two targets is a parse error naming the second" do assertConflict "--namespace-wide" (target ["--scope", "a:b:c", "--namespace-wide", "mori"]),@@ -347,3 +350,68 @@ spaceNamed raw = either (error . Text.unpack) id (mkMemorySpaceId (Text.pack raw))++-- | Help and completion are public CLI output even though the parser values are+-- pure. Keep their structure stable without pinning incidental whitespace.+cliContractTests :: TestTree+cliContractTests =+ testGroup+ "top-level help and completion contract"+ [ testCase "top-level help lists every command at the stable width" do+ rendered <- renderHelp ["--help"]+ mapM_+ (\commandName -> assertBool ("help should list " <> commandName) (commandName `isInfixOf` rendered))+ [ "demo",+ "demo-session",+ "distill",+ "migrate-artifacts",+ "persona",+ "recall",+ "scenes",+ "worker"+ ]+ assertStableWidth rendered,+ testCase "worker help exposes semantic option groups" do+ rendered <- renderHelp ["worker", "--help"]+ assertBool ("worker help should show the AI group:\n" <> rendered) ("AI options" `isInfixOf` rendered)+ assertBool ("worker help should show the execution group:\n" <> rendered) ("Execution mode" `isInfixOf` rendered)+ assertStableWidth rendered,+ testCase "recall help exposes target, query, and output groups" do+ rendered <- renderHelp ["recall", "--help"]+ mapM_+ (\heading -> assertBool ("recall help should show " <> heading <> ":\n" <> rendered) (heading `isInfixOf` rendered))+ ["Recall target", "Query options", "Output options", "AI options"]+ assertStableWidth rendered,+ testCase "bash completion script is generated" do+ script <- renderCompletion "--bash-completion-script"+ assertBool "bash completion script should name kioku" (not (null script) && "kioku" `isInfixOf` script),+ testCase "zsh completion script is generated" do+ script <- renderCompletion "--zsh-completion-script"+ assertBool "zsh completion script should name kioku" (not (null script) && "kioku" `isInfixOf` script),+ testCase "fish completion script is generated" do+ script <- renderCompletion "--fish-completion-script"+ assertBool "fish completion script should name kioku" (not (null script) && "kioku" `isInfixOf` script)+ ]+ where+ renderHelp args =+ case execParserPure cliParserPrefs cliParserInfo args of+ Failure failure -> do+ let (rendered, exitCode) = renderFailure failure "kioku"+ exitCode @?= ExitSuccess+ pure rendered+ Success _ -> assertBool "--help unexpectedly parsed as a command" False >> pure ""+ CompletionInvoked _ -> assertBool "--help unexpectedly invoked completion" False >> pure ""++ renderCompletion flagName =+ case execParserPure cliParserPrefs cliParserInfo [flagName, "kioku"] of+ CompletionInvoked completion -> execCompletion completion "kioku"+ Failure failure -> do+ let (rendered, _) = renderFailure failure "kioku"+ assertBool (flagName <> " failed:\n" <> rendered) False+ pure ""+ Success _ -> assertBool (flagName <> " unexpectedly parsed as a command") False >> pure ""++ assertStableWidth rendered =+ mapM_+ (\line -> assertBool ("help line exceeds 100 columns: " <> show line) (length line <= 100))+ (lines rendered)