Skip to content

feat: ndk cli#495

Merged
frnandu merged 4 commits intomasterfrom
feat/cli
Mar 18, 2026
Merged

feat: ndk cli#495
frnandu merged 4 commits intomasterfrom
feat/cli

Conversation

@frnandu
Copy link
Copy Markdown
Collaborator

@frnandu frnandu commented Mar 18, 2026

A command line client for tool operations, convenient for agents to access using skills.
fixes #491

Summary by CodeRabbit

  • New Features

    • Added a CLI tool for the Nostr Development Kit with an executable entry point.
    • Added a "req" command to query relays for events with filtering options (kind, limit, timeout) and help output.
  • Chores

    • Updated project manifest and development dependencies.
    • Added a .gitignore entry to ignore a local wallet database file.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Adds a new Dart CLI for the Nostr Development Kit: an entrypoint, a command interface and orchestrator, a "req" command implementation for querying relays, pubspec executables/dev_dependency updates, and a .gitignore entry for a local DB file.

Changes

Cohort / File(s) Summary
CLI Framework
packages/ndk/lib/src/cli/cli_command.dart, packages/ndk/lib/src/cli/ndk_cli_app.dart
Adds abstract CliCommand interface and NdkCliApp orchestrator that parses args, routes commands, prints help, creates/destroys Ndk, and handles unknown commands and exit codes.
CLI Command
packages/ndk/lib/src/cli/req_cli_command.dart
Adds ReqCliCommand implementing CliCommand: argument parsing (kinds, limit, timeout), relay normalization/validation, builds filters, issues ndk.requests.query, streams events as JSON, and manages subscription cleanup.
Entrypoint & Manifest
packages/ndk/bin/ndk.dart, packages/ndk/pubspec.yaml
Adds bin/ndk.dart entrypoint bootstrapping NdkCliApp with the req command and exposes an ndk executable in pubspec.yaml; updates dev_dependencies.
Git Configuration
.gitignore
Adds ignore entry for /packages/ndk/wallets_db.db.

Sequence Diagram

sequenceDiagram
    participant User
    participant NdkCliApp
    participant ReqCliCommand
    participant Ndk
    participant Relay

    User->>NdkCliApp: run(['req', ...])
    activate NdkCliApp
    NdkCliApp->>NdkCliApp: parse args, find command
    NdkCliApp->>Ndk: _createNdk()
    activate Ndk
    NdkCliApp->>ReqCliCommand: run(remainingArgs, ndk)
    activate ReqCliCommand
    ReqCliCommand->>ReqCliCommand: parse args, build Filter
    ReqCliCommand->>Ndk: requests.query(filter)
    Ndk->>Relay: open subscription / query
    Relay-->>Ndk: event stream
    Ndk-->>ReqCliCommand: events
    ReqCliCommand->>User: print events as JSON
    ReqCliCommand-->>NdkCliApp: return exitCode
    deactivate ReqCliCommand
    NdkCliApp->>Ndk: destroy()
    deactivate Ndk
    NdkCliApp-->>User: exit with code / print help or errors
    deactivate NdkCliApp
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A tiny CLI hops on the ground,
Commands and relays all gathered round,
I parse and fetch with a twitch and a wink,
Events flow like carrots in a blink,
Hooray — the NDK takes another bound!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR implements an NDK CLI in Dart only [#491] with a command structure (CliCommand, NdkCliApp) and includes a ReqCliCommand for querying relays, providing foundational CLI infrastructure. Clarify whether key storage mechanism requirements from #491 are met; current implementation appears to provide CLI infrastructure but key storage implementation details are not evident from the summaries.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: ndk cli' directly describes the main change: introducing a new CLI feature for the NDK package, which is the primary focus of all file modifications.
Out of Scope Changes check ✅ Passed All changes directly support the NDK CLI objective: CLI infrastructure (.gitignore, bin entry point, base classes), command implementations, app wrapper, and build configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (7)
.gitignore (1)

40-40: Consider whether a pattern-based ignore would be more robust.

While the current specific path works well, you might want to consider whether other database files should also be ignored. For example:

  • *.db - to ignore all database files
  • /packages/ndk/*.db - to ignore all database files in this directory
  • **/wallets_db.db - to ignore this file in any location

However, if only this specific wallet database file should be ignored (and other .db files might legitimately be committed), then the current approach is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 40, The .gitignore currently lists the specific path
/packages/ndk/wallets_db.db; decide whether to broaden it and replace or add a
pattern-based rule: if you want to ignore all DB files globally use *.db, to
ignore all DBs in the ndk package use /packages/ndk/*.db, or to ignore this
filename anywhere use **/wallets_db.db; otherwise keep the exact entry if other
.db files must remain tracked. Update the .gitignore accordingly by adding the
chosen pattern (or leaving the specific path) to reflect the desired scope.
packages/ndk/lib/src/cli/req_cli_command.dart (1)

151-157: Consider documenting the wss:// auto-prefix behavior.

The _parseRelay method silently adds wss:// if the URL doesn't validate directly. While user-friendly, this implicit behavior might be unexpected. Consider either:

  1. Documenting it in the usage/help text, or
  2. Printing a message when auto-prefixing occurs

This is a minor UX consideration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/lib/src/cli/req_cli_command.dart` around lines 151 - 157, The
_parseRelay function silently auto-prefixes input with "wss://" by calling
cleanRelayUrl('wss://$value') when the direct cleanRelayUrl(value) returns null;
make this behavior visible by either adding documentation to the CLI help/usage
text describing the automatic wss:// prefix or by emitting a short informational
message when auto-prefixing occurs (e.g., write to stderr or the CLI logger).
Update the CLI help string(s) and/or modify _parseRelay to call the existing
logging/printing facility when it falls back to 'wss://$value', referencing
_parseRelay and cleanRelayUrl so reviewers can find the change.
packages/ndk/lib/src/cli/ndk_cli_app.dart (2)

73-83: Redundant log level configuration.

LogLevel.error is set twice: once via Logger.setLogLevel() (line 74) and again in NdkConfig.logLevel (line 80). Consider removing one to avoid confusion about which takes precedence.

🔧 Suggested fix - remove duplicate
   Ndk _createNdk() {
-    Logger.setLogLevel(LogLevel.error);
     return Ndk(
       NdkConfig(
         cache: MemCacheManager(),
         eventVerifier: Bip340EventVerifier(),
         bootstrapRelays: const [],
         logLevel: LogLevel.error,
       ),
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/lib/src/cli/ndk_cli_app.dart` around lines 73 - 83, The log
level is being set twice in _createNdk (via Logger.setLogLevel(LogLevel.error)
and NdkConfig(logLevel: LogLevel.error)); remove the redundant setting to avoid
ambiguity — delete the Logger.setLogLevel(LogLevel.error) call inside _createNdk
and rely on the NdkConfig.logLevel property (or alternatively remove the
NdkConfig.logLevel if you prefer global Logger control), updating _createNdk
accordingly while keeping NdkConfig, MemCacheManager, and Bip340EventVerifier
usage unchanged.

49-57: Use appName instead of hardcoded "ndk" for consistency.

The constructor accepts appName as a parameter, but the help text hardcodes "ndk". This creates an inconsistency if the app is instantiated with a different name.

🔧 Suggested fix
   void printHelp([IOSink? out]) {
     out ??= stdout;
     out.writeln('$appName - $description');
-    out.writeln('Usage: ndk <command> [args]');
+    out.writeln('Usage: $appName <command> [args]');
     out.writeln('');
     out.writeln('Commands:');
     for (final command in commands) {
       out.writeln('  ${command.name.padRight(12)} ${command.description}');
     }
     out.writeln('  help         Show this help');
     out.writeln('');
-    out.writeln('Use "ndk <command> --help" for command details.');
+    out.writeln('Use "$appName <command> --help" for command details.');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/lib/src/cli/ndk_cli_app.dart` around lines 49 - 57, The help
text is hardcoded to "ndk" instead of using the constructor parameter appName;
update the out.writeln calls that produce the Usage and help example lines to
interpolate appName (e.g., 'Usage: $appName <command> [args]' and 'Use "$appName
<command> --help" for command details.') so the output reflects the provided
appName; modify the occurrences in the block that iterates commands (uses
out.writeln and commands) to use appName interpolation.
packages/ndk/bin/ndk.dart (2)

15-19: Redundant error message on failure.

When exitCode != 0, NdkCliApp.run() already writes error details to stderr. The additional message here ("ndk CLI failed with exit code...") is redundant and doesn't add useful context.

🔧 Suggested simplification
   final exitCode = await app.run(args);
   if (exitCode != 0) {
-    stderr.writeln('ndk CLI failed with exit code $exitCode');
     exit(exitCode);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/bin/ndk.dart` around lines 15 - 19, The extra stderr.writeln
call is redundant because NdkCliApp.run() already prints errors to stderr;
update the failure branch that checks exitCode returned from app.run(args) to
remove the stderr.writeln('ndk CLI failed with exit code $exitCode') line and
simply call exit(exitCode) when exitCode != 0 (i.e., keep the exit(exitCode) but
drop the additional message in the block that handles the non‑zero exit code for
app.run).

3-4: Consider exporting CLI classes from a public barrel file.

Importing directly from package:ndk/src/cli/... exposes internal implementation paths. If these are intended to be public APIs, export them from a barrel file (e.g., package:ndk/cli.dart or add to package:ndk/ndk.dart).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/bin/ndk.dart` around lines 3 - 4, Replace direct internal
imports with a public barrel: create/update a public export file (e.g.,
package:ndk/cli.dart or add exports to package:ndk/ndk.dart) that exports the
CLI symbols from src/cli (export 'src/cli/ndk_cli_app.dart'; export
'src/cli/req_cli_command.dart';), then update ndk.dart to import from the new
public barrel (import 'package:ndk/cli.dart';) instead of importing
package:ndk/src/cli/ndk_cli_app.dart and
package:ndk/src/cli/req_cli_command.dart; ensure the exported symbols
(NDKCliApp, ReqCliCommand or whatever public class names are declared) are
re-exported by the barrel.
packages/ndk/pubspec.yaml (1)

51-51: Consider pinning the test dependency version.

Using any for the test package allows any version, which could introduce breaking changes unexpectedly. Consider specifying a version constraint like ^1.25.0 for more predictable builds.

🔧 Suggested fix
-  test: any
+  test: ^1.25.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/pubspec.yaml` at line 51, The pubspec currently uses an unpinned
test dependency ("test: any"); update the test dependency entry in
packages/ndk/pubspec.yaml to a specific version constraint (for example change
"test: any" to a caret-constrained version like "^1.25.0") to avoid pulling
breaking changes, then run pub get to lock the dependency; look for the "test"
dependency key in the file when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.gitignore:
- Line 40: The .gitignore currently lists the specific path
/packages/ndk/wallets_db.db; decide whether to broaden it and replace or add a
pattern-based rule: if you want to ignore all DB files globally use *.db, to
ignore all DBs in the ndk package use /packages/ndk/*.db, or to ignore this
filename anywhere use **/wallets_db.db; otherwise keep the exact entry if other
.db files must remain tracked. Update the .gitignore accordingly by adding the
chosen pattern (or leaving the specific path) to reflect the desired scope.

In `@packages/ndk/bin/ndk.dart`:
- Around line 15-19: The extra stderr.writeln call is redundant because
NdkCliApp.run() already prints errors to stderr; update the failure branch that
checks exitCode returned from app.run(args) to remove the stderr.writeln('ndk
CLI failed with exit code $exitCode') line and simply call exit(exitCode) when
exitCode != 0 (i.e., keep the exit(exitCode) but drop the additional message in
the block that handles the non‑zero exit code for app.run).
- Around line 3-4: Replace direct internal imports with a public barrel:
create/update a public export file (e.g., package:ndk/cli.dart or add exports to
package:ndk/ndk.dart) that exports the CLI symbols from src/cli (export
'src/cli/ndk_cli_app.dart'; export 'src/cli/req_cli_command.dart';), then update
ndk.dart to import from the new public barrel (import 'package:ndk/cli.dart';)
instead of importing package:ndk/src/cli/ndk_cli_app.dart and
package:ndk/src/cli/req_cli_command.dart; ensure the exported symbols
(NDKCliApp, ReqCliCommand or whatever public class names are declared) are
re-exported by the barrel.

In `@packages/ndk/lib/src/cli/ndk_cli_app.dart`:
- Around line 73-83: The log level is being set twice in _createNdk (via
Logger.setLogLevel(LogLevel.error) and NdkConfig(logLevel: LogLevel.error));
remove the redundant setting to avoid ambiguity — delete the
Logger.setLogLevel(LogLevel.error) call inside _createNdk and rely on the
NdkConfig.logLevel property (or alternatively remove the NdkConfig.logLevel if
you prefer global Logger control), updating _createNdk accordingly while keeping
NdkConfig, MemCacheManager, and Bip340EventVerifier usage unchanged.
- Around line 49-57: The help text is hardcoded to "ndk" instead of using the
constructor parameter appName; update the out.writeln calls that produce the
Usage and help example lines to interpolate appName (e.g., 'Usage: $appName
<command> [args]' and 'Use "$appName <command> --help" for command details.') so
the output reflects the provided appName; modify the occurrences in the block
that iterates commands (uses out.writeln and commands) to use appName
interpolation.

In `@packages/ndk/lib/src/cli/req_cli_command.dart`:
- Around line 151-157: The _parseRelay function silently auto-prefixes input
with "wss://" by calling cleanRelayUrl('wss://$value') when the direct
cleanRelayUrl(value) returns null; make this behavior visible by either adding
documentation to the CLI help/usage text describing the automatic wss:// prefix
or by emitting a short informational message when auto-prefixing occurs (e.g.,
write to stderr or the CLI logger). Update the CLI help string(s) and/or modify
_parseRelay to call the existing logging/printing facility when it falls back to
'wss://$value', referencing _parseRelay and cleanRelayUrl so reviewers can find
the change.

In `@packages/ndk/pubspec.yaml`:
- Line 51: The pubspec currently uses an unpinned test dependency ("test: any");
update the test dependency entry in packages/ndk/pubspec.yaml to a specific
version constraint (for example change "test: any" to a caret-constrained
version like "^1.25.0") to avoid pulling breaking changes, then run pub get to
lock the dependency; look for the "test" dependency key in the file when making
this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d3b3d04-2266-4a41-8833-f15115f50ac0

📥 Commits

Reviewing files that changed from the base of the PR and between f2cdc15 and 7b199fa.

📒 Files selected for processing (6)
  • .gitignore
  • packages/ndk/bin/ndk.dart
  • packages/ndk/lib/src/cli/cli_command.dart
  • packages/ndk/lib/src/cli/ndk_cli_app.dart
  • packages/ndk/lib/src/cli/req_cli_command.dart
  • packages/ndk/pubspec.yaml

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/ndk/bin/ndk.dart (1)

6-19: Consider wrapping in try-catch for user-friendly error output.

If app.run(args) throws an unhandled exception, users will see a raw stack trace. For a CLI tool—especially one designed for agent use—graceful error handling improves UX.

♻️ Proposed improvement
 Future<void> main(List<String> args) async {
   final app = NdkCliApp(
     appName: 'ndk',
     description: 'Nostr Development Kit command line interface',
     commands: [
       ReqCliCommand(),
     ],
   );
 
-  final exitCode = await app.run(args);
-  if (exitCode != 0) {
-    exit(exitCode);
+  try {
+    final exitCode = await app.run(args);
+    if (exitCode != 0) {
+      exit(exitCode);
+    }
+  } catch (e) {
+    stderr.writeln('Error: $e');
+    exit(1);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/bin/ndk.dart` around lines 6 - 19, Wrap the main function's call
to app.run(args) in a try-catch so uncaught exceptions produce a clean,
user-friendly message and non-zero exit code; specifically, update main (which
constructs NdkCliApp and calls app.run) to catch Exception/Throwable around
await app.run(args) and use stderr or the CLI's logger to print a concise error
message (including optional verbose/stack when a debug flag is set), then exit
with a non-zero exit code if an exception occurred.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ndk/bin/ndk.dart`:
- Around line 6-19: Wrap the main function's call to app.run(args) in a
try-catch so uncaught exceptions produce a clean, user-friendly message and
non-zero exit code; specifically, update main (which constructs NdkCliApp and
calls app.run) to catch Exception/Throwable around await app.run(args) and use
stderr or the CLI's logger to print a concise error message (including optional
verbose/stack when a debug flag is set), then exit with a non-zero exit code if
an exception occurred.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be5f1aeb-3ef0-4884-8bca-3d4796dd7730

📥 Commits

Reviewing files that changed from the base of the PR and between 7b199fa and c784560.

📒 Files selected for processing (2)
  • packages/ndk/bin/ndk.dart
  • packages/ndk/lib/src/cli/req_cli_command.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ndk/lib/src/cli/req_cli_command.dart

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.65%. Comparing base (f2cdc15) to head (c784560).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #495      +/-   ##
==========================================
- Coverage   76.74%   76.65%   -0.10%     
==========================================
  Files         153      153              
  Lines        6407     6407              
==========================================
- Hits         4917     4911       -6     
- Misses       1490     1496       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@frnandu frnandu merged commit fab935d into master Mar 18, 2026
7 of 8 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 31, 2026
Sign up for free to 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.

NDK CLI

1 participant