Skip to content

feat: add offline 'easycord try' command runner - #128

Merged
rolling-codes merged 1 commit into
mainfrom
feat/cli-try
Aug 11, 2026
Merged

feat: add offline 'easycord try' command runner#128
rolling-codes merged 1 commit into
mainfrom
feat/cli-try

Conversation

@rolling-codes

@rolling-codes rolling-codes commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

Adds easycord try <target> <command> — a CLI subcommand that invokes a registered slash command through the existing offline harness (easycord.testing.invoke) and prints the captured responses. No token, no network.

This closes a real gap in the developer toolkit: today you can list interactions (inspect) and preview command sync (sync-plan), but there was no way to actually run a command and see its output from the terminal without booting a live bot.

easycord try bot:bot ping
easycord try bot:bot greet --set name=World --set times=3
easycord try bot:bot config --dm --no-admin      # invoke in a DM, as a non-admin
easycord try bot:bot ping --json

Design

Thin CLI surface over primitives already in the framework — no new core logic:

  • Reuses _load_bot() (easycord/cli.py) for module:object import + validation, exactly like inspect/sync-plan/doctor.
  • Reuses testing.invoke() for offline execution and response capture.
  • New format_try_result() in easycord/formatters.py, following the existing pure data→text formatter pattern; the same dict is emitted by --json.

Behavior:

  • --set key=value (repeatable) → command kwargs, coerced intfloatboolstr.
  • --user / --guild / --dm / --no-admin control the invocation context (defaults: admin, guild 100, matching invoke).
  • Prints each response with an (ephemeral) marker and embed title/description when present.
  • Exits non-zero on an unknown command (listing available commands) or when the command raises — so it doubles as a scriptable smoke check.

Files

File Change
easycord/cli.py cmd_try + _coerce_arg / _parse_set_args / _try_result helpers; try subparser
easycord/formatters.py format_try_result()
tests/test_cli_try.py new — 10 tests
docs/developer-toolkit.md "Run a command offline" section + workflow entry

Test plan

  • New tests: pytest tests/test_cli_try.py -q → 10 passed (text output, --set int coercion via arithmetic, --dm/--no-admin context, embed rendering, --json, unknown-command exit 1 + available list, command-error exit 1, _coerce_arg type inference, --set validation).
  • Full suite: pytest -q → 1837 passed, no regressions.
  • CI lint gate: ruff check easycord tests --select E9,F63,F7,F82 → clean.
  • Pyright (strict): 0 errors on changed files.
  • End-to-end: python -m easycord.cli try demobot:bot ... verified text, ephemeral, multi-response, --json, coercion, and exit codes.

Note

The target module is imported, so bot.run(...) should be guarded behind if __name__ == "__main__": (the easycord new templates already do this) or the import will block. Documented in the new doc section.

Summary by Sourcery

Add a new easycord try CLI subcommand to run registered slash commands offline via the existing testing harness and display their responses, including JSON output.

New Features:

  • Introduce the easycord try CLI subcommand to execute slash commands offline without a Discord token or network connection.
  • Provide JSON-serialisable output for offline command invocations, alongside a human-readable formatter for terminal use.

Enhancements:

  • Extend CLI argument parsing with typed coercion and validation for --set key=value command arguments.
  • Add a formatter to present offline invocation results, including ephemeral markers and embed summaries.

Documentation:

  • Document the offline command runner workflow in the developer toolkit, including usage examples, context defaults, and caveats about importing bot modules.

Tests:

  • Add a dedicated test suite for easycord try, covering argument coercion, context flags, embed rendering, JSON output, and error/unknown-command behavior.

Add an 'easycord try <target> <command>' subcommand that invokes a
registered slash command through the existing offline harness
(easycord.testing.invoke) and prints the captured responses — no token,
no network.

- Reuses _load_bot() and testing.invoke(); no new core logic.
- --set key=value (repeatable) with int/float/bool/str coercion.
- --user/--guild/--dm/--no-admin control the invocation context.
- --json for structured output, matching inspect/sync-plan.
- Exits non-zero on unknown command (lists available) or command error.
- New format_try_result() formatter and tests/test_cli_try.py (10 tests).
@sourcery-ai

sourcery-ai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new easycord try CLI subcommand that runs registered slash commands offline via the existing testing harness, including argument coercion, context flags, structured result formatting, tests, and docs for developer usage.

Sequence diagram for the new easycord try offline command

sequenceDiagram
    actor User
    participant CLI as cli_cmd_try
    participant Bot
    participant Testing as testing_invoke
    participant Formatter as format_try_result

    User->>CLI: easycord try target command [args]
    CLI->>CLI: _load_bot(target)
    CLI-->>Bot: Bot instance
    CLI->>CLI: _parse_set_args(set)
    CLI->>Testing: invoke(Bot, command, user_id, guild_id, is_admin, kwargs)
    alt command not found
        Testing-->>CLI: raise LookupError
        CLI-->>User: stderr "unknown command" (exit 1)
    else command raises
        Testing-->>CLI: raise Exception
        CLI-->>User: stderr "Command ... raised" (exit 1)
    else success
        Testing-->>CLI: ctx (responses)
        CLI->>CLI: _try_result(command, ctx)
        alt --json
            CLI-->>User: print json.dumps(result)
        else text output
            CLI->>Formatter: format_try_result(result)
            Formatter-->>CLI: formatted text
            CLI-->>User: print formatted text
        end
        CLI-->>User: exit 0
    end
Loading

File-Level Changes

Change Details Files
Introduce offline easycord try CLI subcommand that invokes slash commands via the existing testing harness and supports typed --set args and context flags.
  • Add _coerce_arg helper to infer bool/int/float/str for --set values.
  • Add _parse_set_args to turn repeated --set key=value flags into a kwargs dict with validation.
  • Add _try_result to build a JSON-serialisable summary from invoke() responses, including embed metadata.
  • Implement cmd_try to load the bot, call testing.invoke with context options, handle errors, and output text or JSON via format_try_result.
  • Wire a new try subparser into build_parser with arguments for target, command name, --set, user/guild/DM/admin flags, and --json.
  • Import asyncio and format_try_result, and widen typing imports to include Any.
easycord/cli.py
Add formatter for human-readable easycord try output based on the structured result.
  • Implement format_try_result to render command name, numbered responses, ephemeral markers, and embed title/description/field count.
  • Register format_try_result in the module’s __all__ for reuse by the CLI.
easycord/formatters.py
Add tests that cover the new CLI behavior, argument coercion, JSON output, and error paths.
  • Create a fixture that dynamically writes an importable sample bot module with several test commands and cleans up sys.modules.
  • Test basic command execution and text output for ping.
  • Test --set coercion feeding into a command that relies on int arithmetic and ephemeral output.
  • Test default guild/admin context and overrides via --dm and --no-admin.
  • Test embed response rendering in text output.
  • Test unknown-command handling (exit 1, error message listing available commands).
  • Test surfacing command exceptions with type and message on stderr and exit 1.
  • Test --json output shape and contents for a simple command.
  • Test _coerce_arg type inference for int, float, bool, and str.
  • Test _parse_set_args rejects malformed --set entries without = by raising SystemExit.
tests/test_cli_try.py
Document the new offline command runner in the developer toolkit workflow.
  • Add a "Run a command offline" section explaining easycord try usage, context flags, argument coercion, and non-zero exits for unknown commands and errors.
  • Note the need to guard bot.run(...) behind if __name__ == "__main__" because the bot module is imported by try.
  • Update the developer workflow command list to include an example easycord try bot:bot ping invocation.
docs/developer-toolkit.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added the enhancement New feature or request label Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added the offline easycord try command for running bot commands locally.
    • Supports argument overrides, user, guild, and admin context, plus text or JSON output.
    • Displays captured responses, including embeds and ephemeral indicators.
    • Reports invalid commands and execution errors with clear status feedback.
  • Documentation

    • Added guidance and workflow examples for using easycord try offline.

Walkthrough

The pull request adds an offline easycord try command. It supports typed argument overrides, configurable invocation context, text or JSON output, captured response details, error reporting, tests, and developer documentation.

Changes

Offline command testing

Layer / File(s) Summary
Response capture and formatting
easycord/cli.py, easycord/formatters.py
The CLI serializes response content, ephemeral status, and embed metadata. format_try_result renders text output and is exported publicly.
Try command invocation
easycord/cli.py
The try subcommand parses arguments and context controls, loads a bot, invokes slash commands asynchronously, and reports lookup or execution errors.
End-to-end validation and workflow documentation
tests/test_cli_try.py, docs/developer-toolkit.md
Tests cover execution, coercion, contexts, embeds, errors, and JSON output. Documentation describes offline invocation and updates the local workflow.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: tee, tee1339

Poem

A rabbit runs a command at night,
With typed-up flags and output bright.
Embeds unfold, errors show,
JSON hops where text can go.
Offline tests complete the run.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the new offline command runner, its options, behavior, implementation, documentation, and tests.
Title check ✅ Passed The title clearly and concisely identifies the new offline easycord try command runner.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-try

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests labels Aug 11, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="easycord/formatters.py" line_range="70-75" />
<code_context>
+        if embed:
+            title = embed.get("title")
+            description = embed.get("description")
+            field_count = embed.get("fields")
+            if title:
+                lines.append(f"    embed.title: {title}")
+            if description:
+                lines.append(f"    embed.description: {description}")
+            if field_count:
+                lines.append(f"    embed.fields: {field_count}")
+    return "\n".join(lines)
</code_context>
<issue_to_address>
**issue:** Zero embed field counts are skipped due to truthiness check.

Because `if field_count:` treats `0` as false, a valid count of zero will not be shown. If you want to render any provided count, check explicitly for `None` instead, e.g. `if field_count is not None:`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread easycord/formatters.py
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add offline easycord try slash-command runner

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Add easycord try   to invoke slash commands offline.
• Support --set kwargs coercion plus DM/guild/admin context controls.
• Add formatter, docs, and tests for text/JSON output and exit codes.
Diagram

graph TD
  U["Developer"] --> CLI["easycord try"] --> LOAD["_load_bot()"] --> INVOKE["testing.invoke()"] --> CTX["Captured responses"] --> OUT["Text/JSON output"]
  INVOKE --> REG["Bot command registry"]
Loading
High-Level Assessment

The approach is appropriately thin: it reuses _load_bot() for consistent target importing and easycord.testing.invoke() for offline execution/capture, minimizing new core logic and keeping behavior aligned with existing testing primitives. Considered alternatives (a bespoke invocation harness or embedding logic into the Bot/core) would add duplicated logic and increase maintenance surface without clear benefit.

Files changed (4) +296 / -1

Enhancement (2) +141 / -1
cli.pyAdd 'try' subcommand to invoke slash commands offline +113/-1

Add 'try' subcommand to invoke slash commands offline

• Introduces 'cmd_try' wired into the argparse subcommands to run a registered slash command via 'easycord.testing.invoke' using 'asyncio.run'. Adds helpers to parse repeatable '--set key=value' flags with basic type coercion and to build a JSON-serializable response summary for text/JSON output; returns non-zero for unknown commands and command exceptions.

easycord/cli.py

formatters.pyAdd 'format_try_result()' for compact CLI output +28/-0

Add 'format_try_result()' for compact CLI output

• Adds a dedicated formatter that renders the captured response list from 'easycord try', including an ephemeral marker and embed title/description/field count when present. Exports the formatter via '__all__'.

easycord/formatters.py

Tests (1) +130 / -0
test_cli_try.pyAdd CLI tests for offline 'try' command runner +130/-0

Add CLI tests for offline 'try' command runner

• Adds a new test module that builds an importable temporary bot with sample slash commands and validates 'easycord try' behavior. Covers output rendering, '--set' coercion, DM/admin context propagation, embed formatting, '--json' payload shape, unknown-command exit code + available list, and surfacing command exceptions.

tests/test_cli_try.py

Documentation (1) +25 / -0
developer-toolkit.mdDocument offline command execution via 'easycord try' +25/-0

Document offline command execution via 'easycord try'

• Adds a new "Run a command offline" section describing the 'try' subcommand, including examples, context flags, '--set' coercion, and exit-code behavior. Also updates the toolkit workflow list to include 'easycord try'.

docs/developer-toolkit.md

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.34146% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
easycord/formatters.py 87.50% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@augmentcode

augmentcode Bot commented Aug 11, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds an offline easycord try command for exercising registered slash commands from the terminal.

Changes:

  • Loads a bot target and invokes a named command through easycord.testing.invoke.
  • Adds repeatable --set arguments with scalar type coercion.
  • Adds user, guild/DM, and administrator context flags for fake invocations.
  • Prints captured response content, ephemeral state, and embed metadata.
  • Supports structured JSON output and non-zero exits for unknown commands or command errors.
  • Introduces a pure formatter for text output and exports it.
  • Documents the offline workflow and adds CLI coverage for basic invocation paths.
Technical notes: The command intentionally imports the target module and does not require a Discord token or network connection.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread easycord/cli.py
Comment thread easycord/cli.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@easycord/formatters.py`:
- Around line 70-76: Update the field_count condition in the embed formatting
logic to append embed.fields whenever the value is not None, including zero,
while continuing to omit it when the count is unavailable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 320eea9e-1fa6-4423-af79-69a060d58e75

📥 Commits

Reviewing files that changed from the base of the PR and between 5b8d259 and 903224c.

📒 Files selected for processing (4)
  • docs/developer-toolkit.md
  • easycord/cli.py
  • easycord/formatters.py
  • tests/test_cli_try.py
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Analyze (Python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

For any question about the codebase, query graphify against graphify-out/graph.json before opening source, documentation, or context files; do not read the deprecated Wiki or rebuild the graph unless explicitly asked.

Files:

  • easycord/formatters.py
  • docs/developer-toolkit.md
  • easycord/cli.py
  • tests/test_cli_try.py
easycord/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

easycord/**/*.py: Treat easycord/__init__.py as the stable public API. Import consumers from easycord, never from _-prefixed internal modules or helpers.
Use ctx.user or ctx.member, not ctx.author; access ctx.is_admin as a property and never call it as ctx.is_admin().
Store per-guild plugin state in the database layer using per-guild namespaced records, never on the Plugin instance itself.
Never hardcode plugin response strings; retrieve localized text through ctx.t(...).
Always await asynchronous ToolLimiter methods, including check_limit, reset_user, and reset_tool.
Require an explicit ToolSafety annotation when registering an @ai_tool; keep RESTRICTED tools unexposed.
Preserve Discord command-registration constraints before synchronization: names must be at most 32 characters and match [-_a-z0-9], descriptions at most 100 characters, and commands/options at most 25 choices or options as applicable.
Use ServerConfigStore.mutate(guild_id, fn) for every per-guild load-modify-save operation. The callback must be synchronous and local; perform Discord or network I/O outside the lock.
Never perform an unguarded per-guild configuration read-modify-write. Equivalent plugin-specific per-guild locks are acceptable when held across the entire operation, but Discord or network I/O must not occur while the configuration lock is held.
For event-path plugins such as @on("message"), route every destructive action through one governed method that owns rate limiting, channel narrowing, and Discord error handling; Discord exceptions must not escape into the dispatcher.
Before calling .send() on a channel obtained from context or Discord, narrow its type with SENDABLE_CHANNEL_TYPES using isinstance; do not call .send() on an unnarrowed channel.
Initialize LevelsPlugin._cooldowns sentinels to float("-inf"), not 0.0, so the first message always passes.
sync_commands() must raise on removals unless confirm_removals=True is exp...

Files:

  • easycord/formatters.py
  • easycord/cli.py
**/*.{py,md}

📄 CodeRabbit inference engine (AGENTS.md)

Before answering questions about EasyCord architecture, module relationships, data flows, or cross-cutting patterns, query graphify-out/graph.json with /graphify; do not manually read source first.

Files:

  • easycord/formatters.py
  • docs/developer-toolkit.md
  • easycord/cli.py
  • tests/test_cli_try.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use easycord.testing helpers such as invoke, FakeContextBuilder, and PluginTestSuite for command and plugin tests instead of requiring a live Discord connection.
When constructing plugins manually in tests, use MyPlugin.__new__, assign plugin._bot = bot, and then call Plugin.__init__(plugin); do not assign plugin.bot.

tests/**/*.py: Use pytest and pytest-asyncio with asyncio_mode = "auto"; asynchronous tests do not need manual event-loop setup.
Run the full test suite with pytest tests/; individual tests may be run using their test node IDs.

Files:

  • tests/test_cli_try.py
**/test*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain at least 20 tests per plugin, as verified by scripts/verify_plugin_tests.py.

Files:

  • tests/test_cli_try.py
🪛 ast-grep (0.45.1)
easycord/cli.py

[info] 483-483: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result, indent=2, default=str)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🔇 Additional comments (8)
easycord/formatters.py (1)

53-69: LGTM!

Also applies to: 77-77, 115-115

easycord/cli.py (5)

5-5: LGTM!

Also applies to: 16-24


404-427: LGTM!


430-452: LGTM!


455-487: LGTM!


819-842: LGTM!

tests/test_cli_try.py (1)

1-130: LGTM!

docs/developer-toolkit.md (1)

155-178: LGTM!

Also applies to: 281-281

Comment thread easycord/formatters.py
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. LookupError masks command errors 🐞 Bug ☼ Reliability
Description
cmd_try catches LookupError around the whole invoke() call, so KeyError/IndexError raised
by command callbacks are handled by the “unknown command” path and only str(exc) is printed. This
hides the exception type/context and can mislead debugging of real command failures.
Code

easycord/cli.py[R472-475]

+    except LookupError as exc:
+        # invoke() lists the available commands in its message.
+        print(str(exc), file=sys.stderr)
+        return 1
Relevance

●●● Strong

Broad exception handling that misroutes real errors is typically tightened; they’ve accepted similar
error-handling improvements.

PR-#39
PR-#123

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new CLI handler treats any LookupError as “unknown command”, but invoke() both raises
LookupError for unknown commands and executes the callback, so callback-raised LookupError
subclasses will be handled incorrectly at the same catch site.

easycord/cli.py[455-481]
easycord/testing.py[325-353]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`cmd_try()` catches `LookupError` to handle unknown commands, but `LookupError` is also the base class for common runtime errors like `KeyError` and `IndexError`. If a user command raises one of these, it is misrouted into the unknown-command handler and loses the "Command ... raised <Type>" context.

## Issue Context
`easycord.testing.invoke()` raises `LookupError` when a command is not registered, but it also executes the callback; therefore callback-raised `LookupError` subclasses propagate through the same call site.

## Fix Focus Areas
- easycord/cli.py[455-481]
- easycord/testing.py[325-353]

## Suggested fix
- Avoid catching *all* `LookupError` from the entire `invoke()` call.
- Option A (no framework changes): pre-check existence before invoking:
 - `cmd = bot.tree.get_command(args.command)`; if `None`, print the same available list (mirroring `invoke()`), return 1.
 - Then call `invoke()` and let any exceptions (including `KeyError`/`IndexError`) flow to the generic exception handler that prints the exception type.
- Option B: keep the `except LookupError` but only treat it as "unknown command" when the message matches the specific `invoke()` unknown-command format (e.g., contains "is not registered"), otherwise re-raise or fall through to the generic handler.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Coercion order inconsistent 🐞 Bug ⚙ Maintainability
Description
_coerce_arg() and the developer docs claim --set values are coerced int→float→bool→str, but the
implementation checks booleans before numeric parsing. This makes the documented contract inaccurate
and can surprise users expecting the documented coercion order.
Code

easycord/cli.py[R404-407]

+def _coerce_arg(value: str) -> Any:
+    """Coerce a ``--set`` string into int, float, bool, or str (in that order)."""
+    if value.lower() in ("true", "false"):
+        return value.lower() == "true"
Relevance

●●● Strong

Team often fixes doc/behavior mismatches; correcting coercion order or docs is low-risk
maintainability.

PR-#43
PR-#123

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The docs explicitly state the int→float→bool→str order, while the implementation and its own
docstring claim that order but implement bool-first.

docs/developer-toolkit.md[167-172]
easycord/cli.py[404-416]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `_coerce_arg()` docstring and documentation say coercion is `int`, then `float`, then `bool`, then `str`, but the implementation currently checks `bool` first.

## Issue Context
Even if behavior for "true"/"false" happens to remain similar, the mismatch is still a public contract/documentation inconsistency.

## Fix Focus Areas
- easycord/cli.py[404-416]
- docs/developer-toolkit.md[167-172]

## Suggested fix
- Reorder `_coerce_arg()` to try `int(value)` first, then `float(value)`, then parse boolean literals ("true"/"false"), then fall back to `str`.
- Alternatively (if bool-first is desired), update the docstring and docs to reflect the actual order; but simplest is reordering the checks to match existing documentation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 41 rules

Grey Divider

Tip of the day
💡 Did you know, you can group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread easycord/cli.py
Comment thread easycord/cli.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 903224c0f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread easycord/cli.py
Comment thread easycord/cli.py
Comment thread easycord/cli.py
@rolling-codes
rolling-codes merged commit 931f369 into main Aug 11, 2026
37 checks passed
@rolling-codes
rolling-codes deleted the feat/cli-try branch August 11, 2026 18:51
@rolling-codes rolling-codes mentioned this pull request Aug 11, 2026
5 tasks
rolling-codes added a commit that referenced this pull request Aug 11, 2026
Release **v5.61.0**, bundling everything merged since v5.60.0.

## Included
- **#128** — offline `easycord try` CLI (run a slash command without
Discord)
- **#129** — plugin ergonomics helpers: `PluginConfigHelper` (atomic
config via `store.mutate`), decorator stacks (`slash_admin_command`
etc.), `TaskManager`/`TimerManager`
- **#130** — `discord_errors()` middleware + `server_stats`
`store.mutate` migration
- **#131** — `slash_mod_command` now uses `moderate_members` (real perm)
instead of the nonexistent `timeout_members`

## Version bump
`5.60.0 → 5.61.0` (minor: additive features + fixes, no breaking
changes) across `pyproject.toml`, `easycord/__init__.py`, `README.md`,
`docs/getting-started.md`; CHANGELOG entry added.

## Gates (per RELEASE.md)
- [x] `check_release_metadata.py` — no drift
- [x] `ruff check ... --select E9,F63,F7,F82` — clean
- [x] `verify_plugin_tests.py` — thresholds met
- [x] `pytest` — 1865 passed
- [ ] `pyright easycord tests` — 12 errors / 135 warnings, all
**pre-existing on main** and unrelated to this release (CI does not gate
on pyright); noted, not introduced here.

After merge: tag `v5.61.0` and publish a GitHub Release with the built
wheel + sdist. PyPI `twine upload` left as a manual step (requires
credentials).

## Summary by Sourcery

Release EasyCord v5.61.0 with new CLI tooling, plugin ergonomics
helpers, Discord error handling middleware, and minor fixes, plus
corresponding version and metadata updates.

New Features:
- Add offline `easycord try` CLI to run registered slash commands
without Discord connectivity.
- Introduce plugin ergonomics helpers including config mixin, decorator
stacks, and task/timer lifecycle managers.
- Add `discord_errors()` middleware to handle common Discord API errors
with user-facing messages.

Bug Fixes:
- Correct `slash_mod_command` to require the real `moderate_members`
permission instead of nonexistent `timeout_members`.
- Fix `TaskManager.track` race that could leave replacement tasks
untracked and uncancellable.
- Remove unused imports in decorator stacks flagged by static analysis.

Enhancements:
- Route `server_stats` configuration writes through
`ServerConfigStore.mutate` for consolidated locking and consistency.

Build:
- Bump project version to 5.61.0 in pyproject, package metadata, and
module `__version__`, and add corresponding changelog entry.
- Update release metadata entries for wheel and source distribution
artifacts.

Documentation:
- Update README and getting-started docs to reference the v5.61.0
release, download URLs, and badges.

Co-authored-by: tee <Thomas_BIRRELL@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants