Skip to content

feat(cli): add --language flag and prompt to openkb init - #48

Merged
KylinMountain merged 2 commits into
VectifyAI:mainfrom
wooogy-dev:feat/init-language-flag
May 16, 2026
Merged

feat(cli): add --language flag and prompt to openkb init#48
KylinMountain merged 2 commits into
VectifyAI:mainfrom
wooogy-dev:feat/init-language-flag

Conversation

@wooogy-dev

Copy link
Copy Markdown
Contributor

Summary

  • Add --language/-l LANG flag to openkb init so the wiki output language can be set non-interactively (e.g. openkb init --language ko).
  • Add an interactive Wiki language (enter for default en) prompt that runs after the LLM API-key prompt when the flag is
    omitted; pressing enter accepts the configured default.
  • Persist the chosen value to .openkb/config.yaml as language: instead of hard-coding DEFAULT_CONFIG["language"].

Why

Previously openkb init always wrote the default English language to config.yaml, so non-English users had to hand-edit
the file right after initialization. Exposing it at init time — both via flag (for scripts / CI) and via prompt (for the
interactive walkthrough) — removes that manual step.

Test plan

  • tests/test_cli.py — 7/7 pass:
    • test_init_defaults_language_to_en — no flag, default kept.
    • test_init_language_flag_sets_config / test_init_language_short_flag--language / -l skip the prompt and
      write the value.
    • test_init_language_prompt_accepts_input — interactive path writes user input.
    • Pre-existing test_init_creates_structure / test_init_schema_content / test_init_already_exists updated to feed
      prompt input.
  • Full suite (pytest) — 224 passed.
  • Manual: openkb init --language ko and openkb init -l Korean in temp dirs → .openkb/config.yaml reflects the value;
    openkb init --help shows the flag.
@wooogy-dev
wooogy-dev force-pushed the feat/init-language-flag branch from 44c8267 to a2173c3 Compare May 14, 2026 09:53
@KylinMountain

Copy link
Copy Markdown
Collaborator

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@KylinMountain

Copy link
Copy Markdown
Collaborator

A few smaller follow-ups from the same review pass that didn't make the main report — none blocking, but worth considering before merge:

Empty --language "" bypasses the default fallback. The guard if language is None does not catch an explicit empty string from --language "", which then persists to .openkb/config.yaml as language: '' and renders as "Write all content in language." (double space, broken) in compiler.py and query.py. PR #13 already established the pattern for the api_key prompt — .strip() plus a truthiness check. One-liner fix: change the guard to if not language (or strip + re-prompt).

Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init (two newlines for the model + api_key prompts) now Abort! on the new third prompt. The diff updates the tests' input="\n\n" to "\n\n\n" which confirms the break, but downstream CI scripts have no migration notice. Two options: (a) apply the sys.stdin.isatty() pattern from #45 so init's prompts auto-default in non-TTY contexts, or (b) at minimum call this out in the PR description so users know to either pipe an extra newline or pass --language en. A parallel --model flag would also close the asymmetry where init is partially non-interactive.

--language value flows verbatim into LLM system prompts. A flag value like --language "English. Ignore prior instructions..." lands in the agent's system message unchanged. For a single-user KB this isn't a new attack surface (the user already owns their config.yaml), but if OpenKB is ever embedded into automation that passes external input to --language, this becomes a prompt-injection vector. A simple whitelist of common ISO 639-1 codes plus a length cap would close it without limiting practical use.

Address review on #(this PR):

- Strip/validate --language values via _coerce_language: blank/whitespace
  falls back to DEFAULT_CONFIG['language'] instead of persisting an empty
  string that would render as a broken "Write all content in  language."
  prompt in compiler.py and query.py.
- Reject >50-char or control-char --language values so external callers
  cannot smuggle instructions into the LLM system prompt via the flag.
- Gate the language prompt with _stdin_is_tty() (mirrors _stream_to_tty
  from VectifyAI#45) so existing `printf '\n\n' | openkb init` automation keeps
  working instead of aborting on the newly-added third prompt.
- Revert test inputs to "\n\n" as a regression guard for the non-TTY
  path, mock _stdin_is_tty=True for the prompt-exercising test, and add
  coverage for empty/whitespace/unsafe --language values.
@wooogy-dev
wooogy-dev force-pushed the feat/init-language-flag branch from b212020 to 57d8ab6 Compare May 15, 2026 07:12
@wooogy-dev

Copy link
Copy Markdown
Contributor Author

@KylinMountain

Addressed all three in 57d8ab6:

  1. --language callback strips and treats blank as unset, falling back
    to the default.
  2. Language prompt gated on _stdin_is_tty() (mirroring _stream_to_tty
    from fix(query): make openkb query safe for non-TTY stdout (closes #34) #45); non-TTY uses the default, so printf '\n\n' | openkb init
    works again. --model left for a follow-up.
  3. --language rejects >50 chars or \n/\r/\t — blocks injection
    shapes while keeping "Korean" etc. usable.

@KylinMountain KylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@KylinMountain
KylinMountain merged commit 62153db into VectifyAI:main May 16, 2026
KylinMountain added a commit that referenced this pull request May 17, 2026
1. **webbrowser.open return value silently dropped on headless boxes**
   The command printed "Opening GitHub in your browser..." and exited 0
   even when webbrowser.open returned False (no GUI, no $BROWSER, CI
   runner, container without DISPLAY). Now we capture the return value,
   surface "no browser available — copy the URL above" to stderr, and
   only print "Opened GitHub in your browser." after a confirmed True
   return.

2. **_openkb_version was the third copy of the same logic**
   openkb/__init__.py exports __version__ via importlib.metadata with
   fallback "0.0.0+unknown"; openkb/agent/chat.py wraps it as
   _openkb_version(); cli.py was re-implementing it with fallback
   "unknown" — already drifted. Replaced with the same one-liner used
   in chat.py: `from openkb import __version__; return __version__`.
   Now all three call sites agree.

3. **type prompt hung in non-TTY contexts (regression of PR #48)**
   PR #48 introduced _stdin_is_tty() specifically because adding a
   prompt without a TTY check broke automation pipelines. PR #53's
   second prompt (asking for feedback type) repeated the same pattern.
   Now it skips the prompt when stdin isn't a TTY and falls through to
   `--type other`. Pipes / CI work without flags now:

     echo "..." | openkb feedback "msg"   # works, type=other, no label

4 new regression tests:
   - test_feedback_skips_type_prompt_when_stdin_is_not_a_tty
   - test_feedback_warns_when_webbrowser_open_returns_false
   - test_feedback_confirms_when_webbrowser_open_succeeds
   - test_openkb_version_helper_matches_package_version

331 tests pass (327 prior + 4 new).
wooogy-dev added a commit to wooogy-dev/OpenKB that referenced this pull request May 17, 2026
Mirrors the --language pattern from VectifyAI#48 to close the remaining
asymmetry where `openkb init` was only partially non-interactive.

- Add `--model/-m MODEL` flag that skips the interactive model prompt
  when set, persisting the LiteLLM "provider/model" string straight
  to .openkb/config.yaml.
- Gate the model `click.prompt` on `_stdin_is_tty()` so piped/redirected
  callers fall back to the default model without a click prompt failure
  on EOF.
- Add `_coerce_model` validation (max 100 chars, no control chars),
  matching `_coerce_language` so embedded newlines can't corrupt
  logged output or config.yaml.

Now `LLM_API_KEY=... openkb init -m anthropic/claude-sonnet-4-6 -l ko`
is fully non-interactive (api_key prompt remains for security).
KylinMountain pushed a commit that referenced this pull request May 21, 2026
Mirrors the --language pattern from #48 to close the remaining
asymmetry where `openkb init` was only partially non-interactive.

- Add `--model/-m MODEL` flag that skips the interactive model prompt
  when set, persisting the LiteLLM "provider/model" string straight
  to .openkb/config.yaml.
- Gate the model `click.prompt` on `_stdin_is_tty()` so piped/redirected
  callers fall back to the default model without a click prompt failure
  on EOF.
- Add `_coerce_model` validation (max 100 chars, no control chars),
  matching `_coerce_language` so embedded newlines can't corrupt
  logged output or config.yaml.

Now `LLM_API_KEY=... openkb init -m anthropic/claude-sonnet-4-6 -l ko`
is fully non-interactive (api_key prompt remains for security).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants