Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(cli): add `openkb feedback` to file a prefilled GitHub issue by KylinMountain · Pull Request #53 · VectifyAI/OpenKB · GitHub
Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue - #53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain

@KylinMountainKylinMountain commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs"# one-liner
openkb feedback --type bug "..."# tags 'bug' (--type bug|feature|question|other)echo"..."| openkb feedback # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

AlternativeProblem
Auto-create via maintainer PATSecret in source / public client
Hosted feedback APIBackend + moderation + GDPR for an OSS CLI
User's gh CLI authExcludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.
Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.
Usage:
openkb feedback # interactive (stdin)
openkb feedback "openkb add hangs" # one-liner
openkb feedback --type bug "..." # tags 'bug' label
openkb feedback --print-url "..." # SSH / no-browser env
openkb feedback --no-diagnostics "..." # skip env info
The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.
Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes.webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def_openkb_version() ->str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    fromimportlib.metadataimportversion
    returnversion("openkb")
    exceptException:
    return"unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    iffeedback_typeisNone:
    feedback_type=click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics= {} ifno_diagnosticselse_collect_feedback_diagnostics(ctx)

Generated with Claude Code.

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).
The two flags added complexity without changing the day-one user
experience:
- --print-url: URL is already printed before webbrowser.open is
attempted (the "Copy this URL..." line), so SSH/sandbox users can
copy it from the terminal regardless of whether the auto-open
succeeds. The flag was redundant.
- --no-diagnostics: the diagnostics block is intentionally tiny
(openkb version, Python, OS, kb_initialised yes/no) — no paths,
usernames, env vars, or keys. Maintainers benefit from having it
on every issue without exception, and the privacy cost is
negligible.
Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain
KylinMountain merged commit 97b1ca1 into mainMay 17, 2026
1 check passed
@KylinMountain
KylinMountain deleted the feat/feedback-command branch June 2, 2026 08:32
Sign up for freeto 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.

1 participant

@KylinMountain