<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <updated></updated>
  <generator>https://nostr.ae</generator>

  <title>Nostr notes by </title>
  <author>
    <name></name>
  </author>
  <link rel="self" type="application/atom+xml" href="https://nostr.ae/npub1xe6ua0ef6ehwhzvyc06jn5mayjc4hkzhcpqv5p858l3897sx3suqy0d9r6.rss" />
  <link href="https://nostr.ae/npub1xe6ua0ef6ehwhzvyc06jn5mayjc4hkzhcpqv5p858l3897sx3suqy0d9r6" />
  <id>https://nostr.ae/npub1xe6ua0ef6ehwhzvyc06jn5mayjc4hkzhcpqv5p858l3897sx3suqy0d9r6</id>
  <icon></icon>
  <logo></logo>




  <entry>
    <id>https://nostr.ae/nevent1qqsraegjd3862xmppay2pu2z2hw28xcduf7a4km65dzqkqzpkcfh06szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsylee5m</id>
    
      <title type="html">Build manifest for get_file_hash v0.4.4-n34.0</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsraegjd3862xmppay2pu2z2hw28xcduf7a4km65dzqkqzpkcfh06szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsylee5m" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsgaupwrju0jkksk6el52upcsz8ypdrsee7a4njr5whvhxntaek59cx5uv6u&#39;&gt;nevent1q…uv6u&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Build manifest for get_file_hash v0.4.4-n34.0
    </content>
    <updated>2026-04-05T02:58:58&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgaupwrju0jkksk6el52upcsz8ypdrsee7a4njr5whvhxntaek59czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs3f4kjn</id>
    
      <title type="html">&amp;lt;?xml version=&amp;#39;1.0&amp;#39; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgaupwrju0jkksk6el52upcsz8ypdrsee7a4njr5whvhxntaek59czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs3f4kjn" />
    <content type="html">
      &amp;lt;?xml version=&amp;#39;1.0&amp;#39; encoding=&amp;#39;windows-1252&amp;#39;?&amp;gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Copyright (C) 2017 Christopher R. Field.&lt;br/&gt;&lt;br/&gt;  Licensed under the Apache License, Version 2.0 (the &amp;#34;License&amp;#34;);&lt;br/&gt;  you may not use this file except in compliance with the License.&lt;br/&gt;  You may obtain a copy of the License at&lt;br/&gt;&lt;br/&gt;  &lt;a href=&#34;http://www.apache.org/licenses/LICENSE-2.0&#34;&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;  Unless required by applicable law or agreed to in writing, software&lt;br/&gt;  distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,&lt;br/&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br/&gt;  See the License for the specific language governing permissions and&lt;br/&gt;  limitations under the License.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  The &amp;#34;cargo wix&amp;#34; subcommand provides a variety of predefined variables available&lt;br/&gt;  for customization of this template. The values for each variable are set at&lt;br/&gt;  installer creation time. The following variables are available:&lt;br/&gt;&lt;br/&gt;  TargetTriple      = The rustc target triple name.&lt;br/&gt;  TargetEnv         = The rustc target environment. This is typically either&lt;br/&gt;                      &amp;#34;msvc&amp;#34; or &amp;#34;gnu&amp;#34; depending on the toolchain downloaded and&lt;br/&gt;                      installed.&lt;br/&gt;  TargetVendor      = The rustc target vendor. This is typically &amp;#34;pc&amp;#34;, but Rust&lt;br/&gt;                      does support other vendors, like &amp;#34;uwp&amp;#34;.&lt;br/&gt;  CargoTargetBinDir = The complete path to the directory containing the&lt;br/&gt;                      binaries (exes) to include. The default would be&lt;br/&gt;                      &amp;#34;target\release\&amp;#34;. If an explicit rustc target triple is&lt;br/&gt;                      used, i.e. cross-compiling, then the default path would&lt;br/&gt;                      be &amp;#34;target\&amp;lt;CARGO_TARGET&amp;gt;\&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34;,&lt;br/&gt;                      where &amp;#34;&amp;lt;CARGO_TARGET&amp;gt;&amp;#34; is replaced with the &amp;#34;CargoTarget&amp;#34;&lt;br/&gt;                      variable value and &amp;#34;&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34; is replaced with the&lt;br/&gt;                      value from the &amp;#34;CargoProfile&amp;#34; variable. This can also&lt;br/&gt;                      be overridden manually with the &amp;#34;target-bin-dir&amp;#34; flag.&lt;br/&gt;  CargoTargetDir    = The path to the directory for the build artifacts, i.e.&lt;br/&gt;                      &amp;#34;target&amp;#34;.&lt;br/&gt;  CargoProfile      = The cargo profile used to build the binaries&lt;br/&gt;                      (usually &amp;#34;debug&amp;#34; or &amp;#34;release&amp;#34;).&lt;br/&gt;  Version           = The version for the installer. The default is the&lt;br/&gt;                      &amp;#34;Major.Minor.Fix&amp;#34; semantic versioning number of the Rust&lt;br/&gt;                      package.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Please do not remove these pre-processor If-Else blocks. These are used with&lt;br/&gt;  the `cargo wix` subcommand to automatically determine the installation&lt;br/&gt;  destination for 32-bit versus 64-bit installers. Removal of these lines will&lt;br/&gt;  cause installation errors.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&amp;lt;?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFiles64Folder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?else ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFilesFolder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?endif ?&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;Wix xmlns=&amp;#39;&lt;a href=&#34;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&#34;&gt;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&lt;/a&gt;;&lt;br/&gt;&lt;br/&gt;    &amp;lt;Product&lt;br/&gt;        Id=&amp;#39;*&amp;#39;&lt;br/&gt;        Name=&amp;#39;get_file_hash&amp;#39;&lt;br/&gt;        UpgradeCode=&amp;#39;DED69220-26E3-4406-B564-7F2B58C56F57&amp;#39;&lt;br/&gt;        Manufacturer=&amp;#39;gnostr admin@gnostr.org&amp;#39;&lt;br/&gt;        Language=&amp;#39;1033&amp;#39;&lt;br/&gt;        Codepage=&amp;#39;1252&amp;#39;&lt;br/&gt;        Version=&amp;#39;$(var.Version)&amp;#39;&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Package Id=&amp;#39;*&amp;#39;&lt;br/&gt;            Keywords=&amp;#39;Installer&amp;#39;&lt;br/&gt;            Description=&amp;#39;A utility crate providing a procedural macro to compute and embed file hashes at compile time.&amp;#39;&lt;br/&gt;            Manufacturer=&amp;#39;gnostr admin@gnostr.org&amp;#39;&lt;br/&gt;            InstallerVersion=&amp;#39;450&amp;#39;&lt;br/&gt;            Languages=&amp;#39;1033&amp;#39;&lt;br/&gt;            Compressed=&amp;#39;yes&amp;#39;&lt;br/&gt;            InstallScope=&amp;#39;perMachine&amp;#39;&lt;br/&gt;            SummaryCodepage=&amp;#39;1252&amp;#39;&lt;br/&gt;            /&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;MajorUpgrade&lt;br/&gt;            Schedule=&amp;#39;afterInstallInitialize&amp;#39;&lt;br/&gt;            DowngradeErrorMessage=&amp;#39;A newer version of [ProductName] is already installed. Setup will now exit.&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Media Id=&amp;#39;1&amp;#39; Cabinet=&amp;#39;media1.cab&amp;#39; EmbedCab=&amp;#39;yes&amp;#39; DiskPrompt=&amp;#39;CD-ROM #1&amp;#39;/&amp;gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;DiskPrompt&amp;#39; Value=&amp;#39;get_file_hash Installation&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Directory Id=&amp;#39;TARGETDIR&amp;#39; Name=&amp;#39;SourceDir&amp;#39;&amp;gt;&lt;br/&gt;            &amp;lt;Directory Id=&amp;#39;$(var.PlatformProgramFilesFolder)&amp;#39; Name=&amp;#39;PFiles&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;Directory Id=&amp;#39;APPLICATIONFOLDER&amp;#39; Name=&amp;#39;get_file_hash&amp;#39;&amp;gt;&lt;br/&gt;                    &lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                      Enabling the license sidecar file in the installer is a four step process:&lt;br/&gt;&lt;br/&gt;                      1. Uncomment the `Component` tag and its contents.&lt;br/&gt;                      2. Change the value for the `Source` attribute in the `File` tag to a path&lt;br/&gt;                         to the file that should be included as the license sidecar file. The path&lt;br/&gt;                         can, and probably should be, relative to this file.&lt;br/&gt;                      3. Change the value for the `Name` attribute in the `File` tag to the&lt;br/&gt;                         desired name for the file when it is installed alongside the `bin` folder&lt;br/&gt;                         in the installation directory. This can be omitted if the desired name is&lt;br/&gt;                         the same as the file name.&lt;br/&gt;                      4. Uncomment the `ComponentRef` tag with the Id attribute value of &amp;#34;License&amp;#34;&lt;br/&gt;                         further down in this file.&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                    &amp;lt;Component Id=&amp;#39;License&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;File Id=&amp;#39;LicenseFile&amp;#39; Name=&amp;#39;ChangeMe&amp;#39; DiskId=&amp;#39;1&amp;#39; Source=&amp;#39;C:\Path\To\File&amp;#39; KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                    &amp;lt;/Component&amp;gt;&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;&lt;br/&gt;                    &amp;lt;Directory Id=&amp;#39;Bin&amp;#39; Name=&amp;#39;bin&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;Path&amp;#39; Guid=&amp;#39;8DB39A25-8B99-4C25-8CF5-4704353C7C6E&amp;#39; KeyPath=&amp;#39;yes&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;Environment&lt;br/&gt;                                Id=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Name=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Value=&amp;#39;[Bin]&amp;#39;&lt;br/&gt;                                Permanent=&amp;#39;no&amp;#39;&lt;br/&gt;                                Part=&amp;#39;last&amp;#39;&lt;br/&gt;                                Action=&amp;#39;set&amp;#39;&lt;br/&gt;                                System=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary0&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe0&amp;#39;&lt;br/&gt;                                Name=&amp;#39;get_file_hash.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\get_file_hash.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary1&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe1&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary2&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe2&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34-relay.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34-relay.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary3&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe3&amp;#39;&lt;br/&gt;                                Name=&amp;#39;readme.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\readme.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                    &amp;lt;/Directory&amp;gt;&lt;br/&gt;                &amp;lt;/Directory&amp;gt;&lt;br/&gt;            &amp;lt;/Directory&amp;gt;&lt;br/&gt;        &amp;lt;/Directory&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Feature&lt;br/&gt;            Id=&amp;#39;Binaries&amp;#39;&lt;br/&gt;            Title=&amp;#39;Application&amp;#39;&lt;br/&gt;            Description=&amp;#39;Installs all binaries and the license.&amp;#39;&lt;br/&gt;            Level=&amp;#39;1&amp;#39;&lt;br/&gt;            ConfigurableDirectory=&amp;#39;APPLICATIONFOLDER&amp;#39;&lt;br/&gt;            AllowAdvertise=&amp;#39;no&amp;#39;&lt;br/&gt;            Display=&amp;#39;expand&amp;#39;&lt;br/&gt;            Absent=&amp;#39;disallow&amp;#39;&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Uncomment the following `ComponentRef` tag to add the license&lt;br/&gt;              sidecar file to the installer.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;!--&amp;lt;ComponentRef Id=&amp;#39;License&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary0&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary1&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary2&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary3&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;Feature&lt;br/&gt;                Id=&amp;#39;Environment&amp;#39;&lt;br/&gt;                Title=&amp;#39;PATH Environment Variable&amp;#39;&lt;br/&gt;                Description=&amp;#39;Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.&amp;#39;&lt;br/&gt;                Level=&amp;#39;1&amp;#39;&lt;br/&gt;                Absent=&amp;#39;allow&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;ComponentRef Id=&amp;#39;Path&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;/Feature&amp;gt;&lt;br/&gt;        &amp;lt;/Feature&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;SetProperty Id=&amp;#39;ARPINSTALLLOCATION&amp;#39; Value=&amp;#39;[APPLICATIONFOLDER]&amp;#39; After=&amp;#39;CostFinalize&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the following `Icon` and `Property` tags to change the product icon.&lt;br/&gt;&lt;br/&gt;          The product icon is the graphic that appears in the Add/Remove&lt;br/&gt;          Programs control panel for the application.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Icon Id=&amp;#39;ProductICO&amp;#39; SourceFile=&amp;#39;wix\Product.ico&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Property Id=&amp;#39;ARPPRODUCTICON&amp;#39; Value=&amp;#39;ProductICO&amp;#39; /&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;ARPHELPLINK&amp;#39; Value=&amp;#39;&lt;a href=&#34;https://github.com/gnostr-org/get_file_hash&amp;#39;/&amp;gt&#34;&gt;https://github.com/gnostr-org/get_file_hash&amp;#39;/&amp;gt&lt;/a&gt;;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;UI&amp;gt;&lt;br/&gt;            &amp;lt;UIRef Id=&amp;#39;WixUI_FeatureTree&amp;#39;/&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Enabling the EULA dialog in the installer is a three step process:&lt;br/&gt;&lt;br/&gt;                1. Comment out or remove the two `Publish` tags that follow the&lt;br/&gt;                   `WixVariable` tag.&lt;br/&gt;                2. Uncomment the `&amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Path\to\Eula.rft&amp;#39;&amp;gt;` tag further down&lt;br/&gt;                3. Replace the `Value` attribute of the `WixVariable` tag with&lt;br/&gt;                   the path to a RTF file that will be used as the EULA and&lt;br/&gt;                   displayed in the license agreement dialog.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;WelcomeDlg&amp;#39; Control=&amp;#39;Next&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;CustomizeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;CustomizeDlg&amp;#39; Control=&amp;#39;Back&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;WelcomeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;/UI&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Enabling the EULA dialog in the installer requires uncommenting&lt;br/&gt;          the following `WixUILicenseRTF` tag and changing the `Value`&lt;br/&gt;          attribute.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!-- &amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Relative\Path\to\Eula.rtf&amp;#39;/&amp;gt; --&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom banner image across&lt;br/&gt;          the top of each screen. See the WiX Toolset documentation for details&lt;br/&gt;          about customization.&lt;br/&gt;&lt;br/&gt;          The banner BMP dimensions are 493 x 58 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIBannerBmp&amp;#39; Value=&amp;#39;wix\Banner.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom image to the first&lt;br/&gt;          dialog, or screen. See the WiX Toolset documentation for details about&lt;br/&gt;          customization.&lt;br/&gt;&lt;br/&gt;          The dialog BMP dimensions are 493 x 312 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIDialogBmp&amp;#39; Value=&amp;#39;wix\Dialog.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;/Product&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;/Wix&amp;gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:58:45&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsw8wfqsd4kxm8uc3kkvj2jelcj899n85g6c5eddyde3vc8fxpy5tczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrstxzcxm</id>
    
      <title type="html">use std::process::Command; use std::fs; use sha2::{Digest, ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsw8wfqsd4kxm8uc3kkvj2jelcj899n85g6c5eddyde3vc8fxpy5tczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrstxzcxm" />
    <content type="html">
      use std::process::Command;&lt;br/&gt;use std::fs;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn calculate_sha256(file_path: &amp;amp;str) -&amp;gt; String {&lt;br/&gt;    let content = fs::read(file_path).expect(&amp;#34;Unable to read file&amp;#34;);&lt;br/&gt;    let mut hasher = Sha256::new();&lt;br/&gt;    hasher.update(&amp;amp;content);&lt;br/&gt;    hasher.finalize()&lt;br/&gt;        .iter()&lt;br/&gt;        .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;        .collect()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn test_get_file_hash_binary_no_features() {&lt;br/&gt;    let output = Command::new(&amp;#34;cargo&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;run&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--bin&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;get_file_hash&amp;#34;)&lt;br/&gt;        .output()&lt;br/&gt;        .expect(&amp;#34;Failed to execute command&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let stdout = String::from_utf8_lossy(&amp;amp;output.stdout);&lt;br/&gt;    let stderr = String::from_utf8_lossy(&amp;amp;output.stderr);&lt;br/&gt;&lt;br/&gt;    // Assert that the command ran successfully&lt;br/&gt;    assert!(output.status.success(), &amp;#34;Command failed with stderr: {}&amp;#34;, stderr);&lt;br/&gt;&lt;br/&gt;    // Manually calculate the hash of the binary&amp;#39;s source file&lt;br/&gt;    let expected_hash = calculate_sha256(&amp;#34;src/bin/get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains the correct hash&lt;br/&gt;    // Check for the raw hash first&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash), &amp;#34;Output did not contain raw expected hash. Expected: {}, Actual: {}&amp;#34;, expected_hash, stdout);&lt;br/&gt;&lt;br/&gt;    // Then check for the formatted string, including backticks&lt;br/&gt;    // Use a regex-like check for more flexibility with newlines if needed, or refine to exact match&lt;br/&gt;    let expected_hash_line = format!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, expected_hash);&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash_line), &amp;#34;Output did not contain expected hash line. Expected line: {}, Actual: {}&amp;#34;, expected_hash_line, stdout);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    assert!(stdout.contains(&amp;#34;Integrity Verified.&amp;#34;), &amp;#34;Output did not contain &amp;#39;Integrity Verified.&amp;#39;. stdout: {}&amp;#34;, stdout);&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Output from get_file_hash binary (no features):&lt;br/&gt;{}&amp;#34;, stdout);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn test_get_file_hash_binary_with_nostr_feature() {&lt;br/&gt;    let output = Command::new(&amp;#34;cargo&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;run&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--bin&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;get_file_hash&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--features&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;nostr&amp;#34;)&lt;br/&gt;        .output()&lt;br/&gt;        .expect(&amp;#34;Failed to execute command&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let stdout = String::from_utf8_lossy(&amp;amp;output.stdout);&lt;br/&gt;    let stderr = String::from_utf8_lossy(&amp;amp;output.stderr);&lt;br/&gt;&lt;br/&gt;    // Assert that the command ran successfully&lt;br/&gt;    assert!(output.status.success(), &amp;#34;Command failed with stderr: {}&amp;#34;, stderr);&lt;br/&gt;&lt;br/&gt;    // Manually calculate the hash of the binary&amp;#39;s source file&lt;br/&gt;    let expected_hash = calculate_sha256(&amp;#34;src/bin/get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains the correct hash&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash), &amp;#34;Output did not contain raw expected hash. Expected: {}, Actual: {}&amp;#34;, expected_hash, stdout);&lt;br/&gt;&lt;br/&gt;    // Then check for the formatted string, including backticks&lt;br/&gt;    let expected_hash_line = format!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, expected_hash);&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash_line), &amp;#34;Output did not contain expected hash line. Expected line: {}, Actual: {}&amp;#34;, expected_hash_line, stdout);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    assert!(stdout.contains(&amp;#34;Integrity Verified.&amp;#34;), &amp;#34;Output did not contain &amp;#39;Integrity Verified.&amp;#39;. stdout: {}&amp;#34;, stdout);&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Output from get_file_hash binary (with nostr feature):&lt;br/&gt;{}&amp;#34;, stdout);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:58:34&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsw2rs3smt8kwlat2n4m0et8ktfrnwz3fhxpeuxfuqqfkx0h22r7aczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsy44pks</id>
    
      <title type="html">//! A crate providing the `get_file_hash!` procedural macro. //! ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsw2rs3smt8kwlat2n4m0et8ktfrnwz3fhxpeuxfuqqfkx0h22r7aczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsy44pks" />
    <content type="html">
      //! A crate providing the `get_file_hash!` procedural macro.&lt;br/&gt;//!&lt;br/&gt;//! This macro allows you to compute the SHA-256 hash of a file at compile time,&lt;br/&gt;//! embedding the resulting hash string directly into your Rust executable.&lt;br/&gt;&lt;br/&gt;pub use get_file_hash_core::get_file_hash;&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `build.rs` at the time of compilation.&lt;br/&gt;pub const BUILD_HASH: &amp;amp;str = env!(&amp;#34;BUILD_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `Cargo.toml` at the time of compilation.&lt;br/&gt;pub const CARGO_TOML_HASH: &amp;amp;str = env!(&amp;#34;CARGO_TOML_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `src/lib.rs` at the time of compilation.&lt;br/&gt;pub const LIB_HASH: &amp;amp;str = env!(&amp;#34;LIB_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The name of the package as specified in Cargo.toml.&lt;br/&gt;pub const CARGO_PKG_NAME: &amp;amp;str = env!(&amp;#34;CARGO_PKG_NAME&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The version of the package as specified in Cargo.toml.&lt;br/&gt;pub const CARGO_PKG_VERSION: &amp;amp;str = env!(&amp;#34;CARGO_PKG_VERSION&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;/// The git commit hash of the repository at the time of compilation.&lt;br/&gt;pub const GIT_COMMIT_HASH: &amp;amp;str = env!(&amp;#34;GIT_COMMIT_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;/// The git branch of the repository at the time of compilation.&lt;br/&gt;pub const GIT_BRANCH: &amp;amp;str = env!(&amp;#34;GIT_BRANCH&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(test)]&lt;br/&gt;mod tests {&lt;br/&gt;    use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;    use super::*;&lt;br/&gt;&lt;br/&gt;    /// Verifies that the exported CARGO_TOML_HASH is not empty.&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_injected_hash_exists() {&lt;br/&gt;        assert!(!BUILD_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified build.rs Hash:&lt;br/&gt;{}&amp;#34;, BUILD_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_TOML_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Cargo.toml Hash:&lt;br/&gt;{}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!LIB_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified src/lib.rs Hash:\n{}&amp;#34;, LIB_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_PKG_NAME.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Package Name:\n{}&amp;#34;, CARGO_PKG_NAME);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_PKG_VERSION.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Package Version:\n{}&amp;#34;, CARGO_PKG_VERSION);&lt;br/&gt;&lt;br/&gt;        #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;        {&lt;br/&gt;            assert!(!GIT_COMMIT_HASH.is_empty());&lt;br/&gt;            println!(&amp;#34;Verified Git Commit Hash:\n{}&amp;#34;, GIT_COMMIT_HASH);&lt;br/&gt;&lt;br/&gt;            assert!(!GIT_BRANCH.is_empty());&lt;br/&gt;            println!(&amp;#34;Verified Git Branch:\n{}&amp;#34;, GIT_BRANCH);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Tests that the `get_file_hash!` macro correctly computes the SHA-256&lt;br/&gt;    /// hash of `lib.rs` and that it matches a manually computed hash of the&lt;br/&gt;    /// same file.&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_lib_hash() {&lt;br/&gt;        let file_content = include_bytes!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;        let mut hasher = Sha256::new();&lt;br/&gt;        hasher.update(file_content);&lt;br/&gt;        let expected_hash = hasher&lt;br/&gt;            .finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;&lt;br/&gt;        let actual_hash = get_file_hash!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;        assert_eq!(actual_hash, expected_hash);&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:58:23&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsds0l2pqrfjc9gx00wmcw27tawmz2jgky3dju5pj9ntt6xe3nayzgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsmeasah</id>
    
      <title type="html">Relay URL,Latitude,Longitude wot.nostr.party,36.1627,-86.7816 ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsds0l2pqrfjc9gx00wmcw27tawmz2jgky3dju5pj9ntt6xe3nayzgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsmeasah" />
    <content type="html">
      Relay URL,Latitude,Longitude&lt;br/&gt;wot.nostr.party,36.1627,-86.7816&lt;br/&gt;nostr.simplex.icu,50.1109,8.68213&lt;br/&gt;relay.snort.social,53.3498,-6.26031&lt;br/&gt;nostr.stakey.net,52.3676,4.90414&lt;br/&gt;relay.mccormick.cx,52.3563,4.95714&lt;br/&gt;nostr.overmind.lol,43.6532,-79.3832&lt;br/&gt;nostriches.club,43.6532,-79.3832&lt;br/&gt;kanagrovv-pyramid.kozow.com,43.4305,-83.9638&lt;br/&gt;relay.toastr.net,40.8054,-74.0241&lt;br/&gt;relay.primal.net,43.6532,-79.3832&lt;br/&gt;r.bitcoinhold.net,43.6532,-79.3832&lt;br/&gt;wot.dtonon.com,43.6532,-79.3832&lt;br/&gt;santo.iguanatech.net,40.8302,-74.1299&lt;br/&gt;relay2.ngengine.org,43.6532,-79.3832&lt;br/&gt;nostr.oxtr.dev,50.4754,12.3683&lt;br/&gt;simplex.icu,50.1109,8.68213&lt;br/&gt;bitchat.nostr1.com,38.6327,-90.1961&lt;br/&gt;relay.nostu.be,40.4167,-3.70329&lt;br/&gt;nostr.mifen.me,43.6532,-79.3832&lt;br/&gt;relay.vantis.ninja,43.6532,-79.3832&lt;br/&gt;nostr-kyomu-haskell.onrender.com,37.7775,-122.397&lt;br/&gt;nostr-relay.amethyst.name,39.0067,-77.4291&lt;br/&gt;relay.bitcoindistrict.org,43.6532,-79.3832&lt;br/&gt;nos.xmark.cc,50.6924,3.20113&lt;br/&gt;relay01.lnfi.network,35.6764,139.65&lt;br/&gt;nostr-dev.wellorder.net,45.5201,-122.99&lt;br/&gt;nostr.rblb.it,43.7094,10.6582&lt;br/&gt;offchain.pub,39.1585,-94.5728&lt;br/&gt;wot.nostr.place,32.7767,-96.797&lt;br/&gt;fanfares.nostr1.com,38.6327,-90.1961&lt;br/&gt;ephemeral.snowflare.cc,43.6532,-79.3832&lt;br/&gt;relay.visionfusen.org,43.6532,-79.3832&lt;br/&gt;nostr.carroarmato0.be,51.0368,3.21186&lt;br/&gt;social.amanah.eblessing.co,48.1046,11.6002&lt;br/&gt;relay.cyphernomad.com,60.4032,25.0321&lt;br/&gt;nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397&lt;br/&gt;relay.ngengine.org,43.6532,-79.3832&lt;br/&gt;relay.nosto.re,51.1792,5.89444&lt;br/&gt;rusty-uat.siberian-albacore.ts.net:8443,35.6764,139.65&lt;br/&gt;nostr.luisschwab.net,43.6532,-79.3832&lt;br/&gt;nostr.aruku.ovh,1.27994,103.849&lt;br/&gt;srtrelay.c-stellar.net,43.6532,-79.3832&lt;br/&gt;nostr.tagomago.me,3.139,101.687&lt;br/&gt;relay.threenine.services,51.5222,-0.62916&lt;br/&gt;nostr.robosats.org,64.1476,-21.9392&lt;br/&gt;nostr.2b9t.xyz,34.0549,-118.243&lt;br/&gt;strfry.apps3.slidestr.net,40.4167,-3.70329&lt;br/&gt;kitchen.zap.cooking,43.6532,-79.3832&lt;br/&gt;relay.homeinhk.xyz,45.5152,-122.678&lt;br/&gt;relay.seq1.net,43.6532,-79.3832&lt;br/&gt;relay.bornheimer.app,50.1109,8.68213&lt;br/&gt;relay.vrtmrz.net,43.6532,-79.3832&lt;br/&gt;nostr.red5d.dev,43.6532,-79.3832&lt;br/&gt;relay-freeharmonypeople.space,38.7223,-9.13934&lt;br/&gt;aaa-api.freefrom.space/v1/ws,43.6532,-79.3832&lt;br/&gt;dev.relay.edufeed.org,49.4521,11.0767&lt;br/&gt;nostr.myshosholoza.co.za,52.3913,4.66545&lt;br/&gt;nostr.plantroon.com,50.1013,8.62643&lt;br/&gt;wot.dergigi.com,64.1476,-21.9392&lt;br/&gt;npub1spxdug4m3y24hpx5crm0el4zhkk0wafs8kp6m0xu0wecygqej2xqq8gyhx.fips.network,43.6532,-79.3832&lt;br/&gt;nostrelay.circum.space,52.3676,4.90414&lt;br/&gt;relay.dreamith.to,43.6532,-79.3832&lt;br/&gt;relay.nostriches.club,43.6532,-79.3832&lt;br/&gt;nostr-relayrs.gateway.in.th,15.5163,103.194&lt;br/&gt;nostr.chrissexton.org,43.6532,-79.3832&lt;br/&gt;relay.flashapp.me,43.652,-79.3633&lt;br/&gt;nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832&lt;br/&gt;strfry.shock.network,39.0438,-77.4874&lt;br/&gt;relay.snotr.nl:49999,52.0195,4.42946&lt;br/&gt;spatia-arcana.com,34.0362,-118.443&lt;br/&gt;nostr.computingcache.com,34.0356,-118.442&lt;br/&gt;nostr.self-determined.de,53.5,10.25&lt;br/&gt;relay.sharegap.net,43.6532,-79.3832&lt;br/&gt;spookstr2.nostr1.com,38.6327,-90.1961&lt;br/&gt;v-relay.d02.vrtmrz.net,34.6937,135.502&lt;br/&gt;bridge.tagomago.me,3.139,101.687&lt;br/&gt;antiprimal.net,43.6532,-79.3832&lt;br/&gt;relay.nostrdice.com,-33.8688,151.209&lt;br/&gt;purpura.cloud,43.6532,-79.3832&lt;br/&gt;espelho.girino.org,43.6532,-79.3832&lt;br/&gt;relay.mostro.network,40.8302,-74.1299&lt;br/&gt;temp.iris.to,43.6532,-79.3832&lt;br/&gt;pyramid.self-determined.de,53.5,10.25&lt;br/&gt;relay.jeffg.fyi,43.6532,-79.3832&lt;br/&gt;nostr.aruku.kro.kr,37.3589,127.115&lt;br/&gt;chat-relay.zap-work.com,43.6532,-79.3832&lt;br/&gt;relay.islandbitcoin.com,12.8498,77.6545&lt;br/&gt;nostr.zoracle.org,45.6018,-121.185&lt;br/&gt;relay-dev.satlantis.io,40.8302,-74.1299&lt;br/&gt;relay.earthly.city,34.0362,-118.443&lt;br/&gt;speakeasy.cellar.social,49.4543,11.0746&lt;br/&gt;relay.bnos.space,43.6532,-79.3832&lt;br/&gt;relay.henryxplace.eu.org:9988,31.2304,121.474&lt;br/&gt;relay.bitmacro.cloud,43.6532,-79.3832&lt;br/&gt;nostr.thebiglake.org,32.71,-96.6745&lt;br/&gt;relay.lab.rytswd.com,49.4543,11.0746&lt;br/&gt;nostr.nadajnik.org,50.1109,8.68213&lt;br/&gt;relay.evanverma.com,40.8302,-74.1299&lt;br/&gt;relay2.angor.io,48.1046,11.6002&lt;br/&gt;prl.plus,55.7628,37.5983&lt;br/&gt;myvoiceourstory.org,37.3598,-121.981&lt;br/&gt;relay.arx-ccn.com,50.4754,12.3683&lt;br/&gt;wot.sudocarlos.com,43.6532,-79.3832&lt;br/&gt;relayrs.notoshi.win,43.6532,-79.3832&lt;br/&gt;nostrcity-club.fly.dev,48.8575,2.35138&lt;br/&gt;relay.tagayasu.xyz,45.4215,-75.6972&lt;br/&gt;nostr.blankfors.se,60.1699,24.9384&lt;br/&gt;nrs-01.darkcloudarcade.com,39.1008,-94.5811&lt;br/&gt;relay.lightning.pub,39.0438,-77.4874&lt;br/&gt;nostr-02.yakihonne.com,1.32123,103.695&lt;br/&gt;relay.nostrverse.net,43.6532,-79.3832&lt;br/&gt;nostr.wecsats.io,43.6532,-79.3832&lt;br/&gt;relay.illuminodes.com,47.6062,-122.332&lt;br/&gt;api.freefrom.space/v1/ws,43.6532,-79.3832&lt;br/&gt;nostr-relay.psfoundation.info,39.0438,-77.4874&lt;br/&gt;relay.samt.st,40.8302,-74.1299&lt;br/&gt;nostr-relay.cbrx.io,43.6532,-79.3832&lt;br/&gt;inbox.mycelium.social,38.627,-90.1994&lt;br/&gt;relay.anmore.me,49.281,-123.117&lt;br/&gt;no.str.cr,10.074,-84.2155&lt;br/&gt;nstr.a0a1.space,52.3563,4.95714&lt;br/&gt;relay.typedcypher.com,51.5072,-0.127586&lt;br/&gt;relay.bitmacro.pro,43.6532,-79.3832&lt;br/&gt;relay.nostrzh.org,43.6532,-79.3832&lt;br/&gt;ynostr.yael.at,60.1699,24.9384&lt;br/&gt;nostr-relay.zeabur.app,25.0797,121.234&lt;br/&gt;dynasty.libretechsystems.xyz,55.4724,9.87335&lt;br/&gt;nostr.bitcoiner.social,47.6743,-117.112&lt;br/&gt;nostr.girino.org,43.6532,-79.3832&lt;br/&gt;nostr2.girino.org,43.6532,-79.3832&lt;br/&gt;nostr-verified.wellorder.net,45.5201,-122.99&lt;br/&gt;relay.fundstr.me,42.3601,-71.0589&lt;br/&gt;relay.mapboss.co.th,13.7234,100.784&lt;br/&gt;relay.qstr.app,51.5072,-0.127586&lt;br/&gt;nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832&lt;br/&gt;relay.klabo.world,47.674,-122.122&lt;br/&gt;relay.minibolt.info,43.6532,-79.3832&lt;br/&gt;x.kojira.io,43.6532,-79.3832&lt;br/&gt;relay-dev.gulugulu.moe,43.6532,-79.3832&lt;br/&gt;relay.nostriot.com,41.5695,-83.9786&lt;br/&gt;relayone.soundhsa.com,39.1008,-94.5811&lt;br/&gt;nr.yay.so,46.2126,6.1154&lt;br/&gt;relay.bithome.site,52.3563,4.95714&lt;br/&gt;relay.damus.io,43.6532,-79.3832&lt;br/&gt;nostr.mikoshi.de,50.1109,8.68213&lt;br/&gt;nostr.defucc.me,50.1109,8.68213&lt;br/&gt;relay.malxte.de,52.52,13.405&lt;br/&gt;relay.orangepill.ovh,49.1689,-0.358841&lt;br/&gt;bbw-nostr.xyz,41.5284,-87.4237&lt;br/&gt;kasztanowa.bieda.it,43.6532,-79.3832&lt;br/&gt;bitcoiner.social,47.6743,-117.112&lt;br/&gt;relay.lacompagniemaximus.com,45.3147,-73.8785&lt;br/&gt;relay.mostr.pub,43.6532,-79.3832&lt;br/&gt;relay.lanavault.space,60.1699,24.9384&lt;br/&gt;kotukonostr.onrender.com,37.7775,-122.397&lt;br/&gt;relay.ditto.pub,43.6532,-79.3832&lt;br/&gt;relay.erybody.com,41.4513,-81.7021&lt;br/&gt;nostr.dlcdevkit.com,40.0992,-83.1141&lt;br/&gt;ribo.us.nostria.app,41.5868,-93.625&lt;br/&gt;relay.paulstephenborile.com,49.4543,11.0746&lt;br/&gt;testnet-relay.samt.st,40.8302,-74.1299&lt;br/&gt;relay.purplefrog.cloud,35.6916,139.768&lt;br/&gt;relay.agorist.space,52.3734,4.89406&lt;br/&gt;nostr-relay.zimage.com,34.0549,-118.243&lt;br/&gt;nostr.azzamo.net,52.2633,21.0283&lt;br/&gt;strfry.elswa-dev.online,50.1109,8.68213&lt;br/&gt;wot.shaving.kiwi,43.6532,-79.3832&lt;br/&gt;okn.czas.plus,50.1109,8.68213&lt;br/&gt;bcast.seutoba.com.br,43.6532,-79.3832&lt;br/&gt;relay.sigit.io,50.4754,12.3683&lt;br/&gt;syb.lol,34.0549,-118.243&lt;br/&gt;relay.libernet.app,43.6532,-79.3832&lt;br/&gt;relay.angor.io,48.1046,11.6002&lt;br/&gt;relay.staging.commonshub.brussels,49.4543,11.0746&lt;br/&gt;strfry.atlantislabs.space,43.6532,-79.3832&lt;br/&gt;nostr.wom.wtf,43.6532,-79.3832&lt;br/&gt;nostrride.io,37.3986,-121.964&lt;br/&gt;nostr.dpinkerton.com,39.1008,-94.5811&lt;br/&gt;r.0kb.io,32.789,-96.7989&lt;br/&gt;nostr.hekster.org,37.3986,-121.964&lt;br/&gt;satsage.xyz,37.3986,-121.964&lt;br/&gt;nostr.islandarea.net,35.4669,-97.6473&lt;br/&gt;ve.agorawlc.com,50.4754,12.3683&lt;br/&gt;relay.openfarmtools.org,60.1699,24.9384&lt;br/&gt;top.testrelay.top,43.6532,-79.3832&lt;br/&gt;relay-rpi.edufeed.org,49.4521,11.0767&lt;br/&gt;pyramid.cult.cash,32.9483,-96.7299&lt;br/&gt;relay.edino.net,56.6268,47.9193&lt;br/&gt;nostr.snowbla.de,60.1699,24.9384&lt;br/&gt;relay.wavefunc.live,39.7392,-104.99&lt;br/&gt;tenex.chat,50.4754,12.3683&lt;br/&gt;relay.getsafebox.app,43.6532,-79.3832&lt;br/&gt;nostr.bond,50.1109,8.68213&lt;br/&gt;nostrelites.org,41.8781,-87.6298&lt;br/&gt;relay.plebeian.market,50.1109,8.68213&lt;br/&gt;relay.laantungir.net,-19.4692,-42.5315&lt;br/&gt;relay.decentnewsroom.com,50.4754,12.3683&lt;br/&gt;nostr-relay.nextblockvending.com,47.2343,-119.853&lt;br/&gt;relay.spacetomatoes.net,42.3601,-71.0589&lt;br/&gt;nostrbtc.com,43.6532,-79.3832&lt;br/&gt;relay.puresignal.news,43.6532,-79.3832&lt;br/&gt;relay-testnet.k8s.layer3.news,37.3387,-121.885&lt;br/&gt;relay.binaryrobot.com,43.6532,-79.3832&lt;br/&gt;relay.wavlake.com,41.2619,-95.8608&lt;br/&gt;inbox.scuba323.com,40.8218,-74.45&lt;br/&gt;nostr.spaceshell.xyz,43.6532,-79.3832&lt;br/&gt;relay.nostr.place,32.7767,-96.797&lt;br/&gt;holland-excited-charming-experiencing.trycloudflare.com,43.6532,-79.3832&lt;br/&gt;theoutpost.life,64.1476,-21.9392&lt;br/&gt;relay.fckstate.net,59.3293,18.0686&lt;br/&gt;bcast.girino.org,43.6532,-79.3832&lt;br/&gt;discovery.us.nostria.app,52.3676,4.90414&lt;br/&gt;relay.bullishbounty.com,43.6532,-79.3832&lt;br/&gt;nostr.88mph.life,51.5072,-0.127586&lt;br/&gt;nostr.tadryanom.me,43.6532,-79.3832&lt;br/&gt;nostr.sathoarder.com,48.5734,7.75211&lt;br/&gt;relay.nostr.net,43.6532,-79.3832&lt;br/&gt;zw.agorawlc.com,50.4754,12.3683&lt;br/&gt;relay.internationalright-wing.org,-22.5022,-48.7114&lt;br/&gt;nostr.vulpem.com,49.4543,11.0746&lt;br/&gt;wot.codingarena.top,50.4754,12.3683&lt;br/&gt;reraw.pbla2fish.cc,43.6532,-79.3832&lt;br/&gt;plebchain.club,43.6532,-79.3832&lt;br/&gt;orly-relay.imwald.eu,48.8575,2.35138&lt;br/&gt;relay.satnam.pub,43.6532,-79.3832&lt;br/&gt;cs-relay.nostrdev.com,50.4754,12.3683&lt;br/&gt;schnorr.me,43.6532,-79.3832&lt;br/&gt;nostr-relay.online,43.6532,-79.3832&lt;br/&gt;relay.routstr.com,43.6532,-79.3832&lt;br/&gt;relay.ohstr.com,43.6532,-79.3832&lt;br/&gt;relay.lanacoin-eternity.com,40.8302,-74.1299&lt;br/&gt;wot.nostr.net,43.6532,-79.3832&lt;br/&gt;nostr.ps1829.com,33.8851,130.883&lt;br/&gt;yabu.me,35.6092,139.73&lt;br/&gt;soloco.nl,43.6532,-79.3832&lt;br/&gt;librerelay.aaroniumii.com,43.6532,-79.3832&lt;br/&gt;relay.mmwaves.de,48.8575,2.35138&lt;br/&gt;relay.artx.market,43.6548,-79.3885&lt;br/&gt;nostr.jerrynya.fun,31.2304,121.474&lt;br/&gt;relay-arg.zombi.cloudrodion.com,1.35208,103.82&lt;br/&gt;relay.edufeed.org,49.4521,11.0767&lt;br/&gt;discovery.eu.nostria.app,52.3676,4.90414&lt;br/&gt;relay.layer.systems,49.0291,8.35695&lt;br/&gt;nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874&lt;br/&gt;relay.0xchat.com,43.6532,-79.3832&lt;br/&gt;nos.lol,50.4754,12.3683&lt;br/&gt;lightning.red,53.3498,-6.26031&lt;br/&gt;slick.mjex.me,39.0418,-77.4744&lt;br/&gt;relay.boredvictor.xyz,41.3888,2.15899&lt;br/&gt;nostr.rtvslawenia.com,49.4543,11.0746&lt;br/&gt;relay.mitchelltribe.com,39.0438,-77.4874&lt;br/&gt;nostr.4rs.nl,49.0291,8.35696&lt;br/&gt;relay.olas.app,50.4754,12.3683&lt;br/&gt;memlay.v0l.io,53.3498,-6.26031&lt;br/&gt;nostr-01.yakihonne.com,1.29524,103.79&lt;br/&gt;relay.satmaxt.xyz,43.6532,-79.3832&lt;br/&gt;nostrcheck.tnsor.network,43.6532,-79.3832&lt;br/&gt;relay.guggero.org,46.0037,8.95105&lt;br/&gt;ai.techunder.tech:56711,22.5429,114.06&lt;br/&gt;premium.primal.net,43.6532,-79.3832&lt;br/&gt;nostr.tac.lol,47.4748,-122.273&lt;br/&gt;relay.zone667.com,60.1699,24.9384&lt;br/&gt;nostr-relay.gateway.in.th,15.5163,103.194&lt;br/&gt;vault.iris.to,43.6532,-79.3832&lt;br/&gt;strfry.bonsai.com,37.8716,-122.273&lt;br/&gt;ribo.eu.nostria.app,52.3676,4.90414&lt;br/&gt;relay.wellorder.net,45.5201,-122.99&lt;br/&gt;relay.tapestry.ninja,40.8054,-74.0241&lt;br/&gt;relay.dwadziesciajeden.pl,52.2297,21.0122&lt;br/&gt;relay.satlantis.io,32.8769,-80.0114&lt;br/&gt;nostr.pbfs.io,50.4754,12.3683&lt;br/&gt;freelay.sovbit.host,64.1476,-21.9392&lt;br/&gt;articles.layer3.news,37.3387,-121.885&lt;br/&gt;nostr.na.social,43.6532,-79.3832&lt;br/&gt;relay.fountain.fm,43.6532,-79.3832&lt;br/&gt;dev.relay.stream,43.6532,-79.3832&lt;br/&gt;nostr.n7ekb.net,36.1527,-95.9902&lt;br/&gt;relay5.bitransfer.org,43.6532,-79.3832&lt;br/&gt;relay.og.coop,43.6532,-79.3832&lt;br/&gt;nostr-server-production.up.railway.app,45.5019,-73.5674&lt;br/&gt;bucket.coracle.social,37.7775,-122.397&lt;br/&gt;relay.gulugulu.moe,43.6532,-79.3832&lt;br/&gt;relay.nostr-check.me,43.6532,-79.3832&lt;br/&gt;nostr.faultables.net,43.6532,-79.3832&lt;br/&gt;strfry.openhoofd.nl,51.9229,4.40833&lt;br/&gt;nostr.rblb.it:7777,43.7094,10.6582&lt;br/&gt;relay.nostrcheck.me,43.6532,-79.3832&lt;br/&gt;0x-nostr-relay.fly.dev,48.8575,2.35138&lt;br/&gt;nostr.thalheim.io,60.1699,24.9384&lt;br/&gt;relay-nl.zombi.cloudrodion.com,50.8943,6.06237&lt;br/&gt;relay.shadowbip.com,51.5072,-0.127586&lt;br/&gt;nostr-relay.corb.net,38.8353,-104.822&lt;br/&gt;purplerelay.com,43.6532,-79.3832&lt;br/&gt;nostr-pub.wellorder.net,45.5201,-122.99&lt;br/&gt;herbstmeister.com,34.0549,-118.243&lt;br/&gt;nostrcheck.me,43.6532,-79.3832&lt;br/&gt;pyramid.nostr.technology,52.3947,4.66399&lt;br/&gt;nostr.spicyz.io,43.6532,-79.3832&lt;br/&gt;nrs-02.darkcloudarcade.com,39.9526,-75.1652&lt;br/&gt;nestr.nedao.ch,47.0151,6.98832&lt;br/&gt;nostr.nodesmap.com,59.3327,18.0656&lt;br/&gt;nittom.nostr1.com,38.6327,-90.1961&lt;br/&gt;public.crostr.com,43.6532,-79.3832&lt;br/&gt;relay.cypherflow.ai,48.8575,2.35138&lt;br/&gt;nostr.bitczat.pl,60.1699,24.9384&lt;br/&gt;relayone.geektank.ai,39.1008,-94.5811&lt;br/&gt;testrelay.era21.space,43.6532,-79.3832&lt;br/&gt;relay.npubhaus.com,43.6532,-79.3832&lt;br/&gt;relay.bitmacro.io,48.8566,2.35222&lt;br/&gt;nostr.data.haus,50.4754,12.3683&lt;br/&gt;relay.credenso.cafe,43.3601,-80.3127&lt;br/&gt;relay.ru.ac.th,13.7607,100.627&lt;br/&gt;relay-fra.zombi.cloudrodion.com,48.8566,2.35222&lt;br/&gt;nostr.chaima.info,50.1109,8.68213&lt;br/&gt;nostr.mom,50.4754,12.3683
    </content>
    <updated>2026-04-05T02:58:10&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsq24z0s2ndplctegmfy4efka38sz2xgxhcjff4qcsajlsut3nz64szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvdkqkd</id>
    
      <title type="html">#![cfg(feature = &amp;#34;nostr&amp;#34;)] use frost_secp256k1_tr as ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsq24z0s2ndplctegmfy4efka38sz2xgxhcjff4qcsajlsut3nz64szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvdkqkd" />
    <content type="html">
      #![cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;&lt;br/&gt;use frost_secp256k1_tr as frost;&lt;br/&gt;use frost::keys::PublicKeyPackage;&lt;br/&gt;use frost::round2::SignatureShare;&lt;br/&gt;use frost::SigningPackage;&lt;br/&gt;use hex;&lt;br/&gt;use rand::thread_rng;&lt;br/&gt;use std::collections::BTreeMap;&lt;br/&gt;use sha2::Sha256;&lt;br/&gt;use serde_json;&lt;br/&gt;use sha2::Digest;&lt;br/&gt;&lt;br/&gt;pub fn process_relay_share(&lt;br/&gt;    relay_payload_hex: &amp;amp;str,&lt;br/&gt;    signer_id_u16: u16,&lt;br/&gt;    _signing_package: &amp;amp;SigningPackage,&lt;br/&gt;    _pubkey_package: &amp;amp;PublicKeyPackage,&lt;br/&gt;) -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    // In a real scenario, this function would deserialize the share, perform&lt;br/&gt;    // individual verification, and store it for aggregation.&lt;br/&gt;    // For this example, we&amp;#39;ll just acknowledge receipt.&lt;br/&gt;    let _share_bytes = hex::decode(relay_payload_hex)?;&lt;br/&gt;    let _share = SignatureShare::deserialize(&amp;amp;_share_bytes)?;&lt;br/&gt;    let _identifier = frost::Identifier::try_from(signer_id_u16)?;&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;✅ Share from Signer {} processed (simplified).&amp;#34;, signer_id_u16);&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn simulate_frost_mailbox_coordinator() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    let mut rng = thread_rng();&lt;br/&gt;    let (max_signers, min_signers) = (2, 2);&lt;br/&gt;&lt;br/&gt;    let (shares, pubkey_package) = frost::keys::generate_with_dealer(&lt;br/&gt;        max_signers,&lt;br/&gt;        min_signers,&lt;br/&gt;        frost::keys::IdentifierList::Default,&lt;br/&gt;        &amp;amp;mut rng,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    let signer1_id = frost::Identifier::try_from(1 as u16)?;&lt;br/&gt;    let key_package1: frost::keys::KeyPackage = shares[&amp;amp;signer1_id].clone().try_into()?;&lt;br/&gt;    let signer2_id = frost::Identifier::try_from(2 as u16)?;&lt;br/&gt;    let key_package2: frost::keys::KeyPackage = shares[&amp;amp;signer2_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    let message = b&amp;#34;BIP-64MOD: Anchor Data Proposal v1&amp;#34;;&lt;br/&gt;&lt;br/&gt;    let (nonces1, comms1) = frost::round1::commit(key_package1.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    let (nonces2, comms2) = frost::round1::commit(key_package2.signing_share(), &amp;amp;mut rng);&lt;br/&gt;&lt;br/&gt;    let mut session_commitments = BTreeMap::new();&lt;br/&gt;    session_commitments.insert(signer1_id, comms1);&lt;br/&gt;    session_commitments.insert(signer2_id, comms2);&lt;br/&gt;&lt;br/&gt;    let signing_package = frost::SigningPackage::new(session_commitments.clone(), message);&lt;br/&gt;&lt;br/&gt;    let share1 = frost::round2::sign(&amp;amp;signing_package, &amp;amp;nonces1, &amp;amp;key_package1)?;&lt;br/&gt;    let share1_hex = hex::encode(share1.serialize());&lt;br/&gt;&lt;br/&gt;    let share2 = frost::round2::sign(&amp;amp;signing_package, &amp;amp;nonces2, &amp;amp;key_package2)?;&lt;br/&gt;    let share2_hex = hex::encode(share2.serialize());&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Coordinator listening for Nostr events (simulated)...&amp;#34;);&lt;br/&gt;&lt;br/&gt;    process_relay_share(&amp;amp;share1_hex, 1_u16, &amp;amp;signing_package, &amp;amp;pubkey_package)?;&lt;br/&gt;    process_relay_share(&amp;amp;share2_hex, 2_u16, &amp;amp;signing_package, &amp;amp;pubkey_package)?;&lt;br/&gt;    println!(&amp;#34;All required shares processed. Coordinator would now aggregate.&amp;#34;);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Simulates a Signer producing a FROST signature share and preparing a Nostr event&lt;br/&gt;/// to be sent to a coordinator via a &amp;#34;mailbox&amp;#34; relay.&lt;br/&gt;///&lt;br/&gt;/// In a real ROAST setup, signers would generate their share and post it&lt;br/&gt;/// encrypted (e.g., using NIP-44) to a coordinator&amp;#39;s &amp;#34;mailbox&amp;#34; on a Nostr relay.&lt;br/&gt;/// This function demonstrates the creation of the signature share and the&lt;br/&gt;/// construction of a *simplified* Nostr event JSON.&lt;br/&gt;///&lt;br/&gt;/// # Arguments&lt;br/&gt;///&lt;br/&gt;/// * `_identifier` - The FROST identifier of the signer. (Currently unused in this specific function body).&lt;br/&gt;/// * `signing_package` - The FROST signing package received from the coordinator.&lt;br/&gt;/// * `nonces` - The signer&amp;#39;s nonces generated in Round 1.&lt;br/&gt;/// * `key_package` - The signer&amp;#39;s FROST key package.&lt;br/&gt;/// * `coordinator_pubkey` - The hex-encoded public key of the ROAST coordinator,&lt;br/&gt;///                          used to tag the Nostr event.&lt;br/&gt;///&lt;br/&gt;/// # Returns&lt;br/&gt;///&lt;br/&gt;/// A `Result` containing the JSON string of the Nostr event if successful,&lt;br/&gt;/// or a `Box&amp;lt;dyn std::error::Error&amp;gt;` if an error occurs.&lt;br/&gt;pub fn create_signer_event(&lt;br/&gt;    _identifier: frost::Identifier,&lt;br/&gt;    signing_package: &amp;amp;frost::SigningPackage,&lt;br/&gt;    nonces: &amp;amp;frost::round1::SigningNonces,&lt;br/&gt;    key_package: &amp;amp;frost::keys::KeyPackage,&lt;br/&gt;    coordinator_pubkey: &amp;amp;str, // The Hex pubkey of the ROAST coordinator&lt;br/&gt;) -&amp;gt; Result&amp;lt;String, Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;&lt;br/&gt;    // 1. Generate the partial signature share (Round 2 of FROST)&lt;br/&gt;    // This share is the core cryptographic output from the signer.&lt;br/&gt;    let share = frost::round2::sign(signing_package, nonces, key_package)?;&lt;br/&gt;    let share_bytes = share.serialize();&lt;br/&gt;    let share_hex = hex::encode(share_bytes);&lt;br/&gt;&lt;br/&gt;    // 2. Create a Session ID to tag the event&lt;br/&gt;    // This ID is derived from the signing package hash, allowing the coordinator&lt;br/&gt;    // to correlate shares belonging to the same signing session.&lt;br/&gt;    let mut hasher = Sha256::new();&lt;br/&gt;    hasher.update(signing_package.serialize()?);&lt;br/&gt;    let session_id = hex::encode(hasher.finalize());&lt;br/&gt;&lt;br/&gt;    // 3. Construct the Nostr Event JSON (Simplified)&lt;br/&gt;    // This JSON represents the event that a signer would post to a relay.&lt;br/&gt;    // In a production ROAST system, the &amp;#39;content&amp;#39; field (the signature share)&lt;br/&gt;    // would be encrypted for the coordinator using NIP-44.&lt;br/&gt;    let event = serde_json::json!({&lt;br/&gt;        &amp;#34;kind&amp;#34;: 4, // Example: Using Kind 4 (Private Message), though custom Kinds could be used for Sovereign Stack.&lt;br/&gt;        &amp;#34;pubkey&amp;#34;: hex::encode(key_package.verifying_key().serialize()?.as_slice()), // Signer&amp;#39;s public key&lt;br/&gt;        &amp;#34;created_at&amp;#34;: 1712050000, // Example timestamp&lt;br/&gt;        &amp;#34;tags&amp;#34;: [&lt;br/&gt;            [&amp;#34;p&amp;#34;, coordinator_pubkey],       // &amp;#39;p&amp;#39; tag: Directs the event to the coordinator.&lt;br/&gt;            [&amp;#34;i&amp;#34;, session_id],               // &amp;#39;i&amp;#39; tag: Provides a session identifier for filtering/requests.&lt;br/&gt;            [&amp;#34;t&amp;#34;, &amp;#34;frost-signature-share&amp;#34;]   // &amp;#39;t&amp;#39; tag: A searchable label for the event type.&lt;br/&gt;        ],&lt;br/&gt;        &amp;#34;content&amp;#34;: share_hex, // The actual signature share (would be encrypted in production).&lt;br/&gt;        &amp;#34;id&amp;#34;: &amp;#34;...&amp;#34;, // Event ID (filled by relay upon publishing)&lt;br/&gt;        &amp;#34;sig&amp;#34;: &amp;#34;...&amp;#34; // Event signature (filled by relay upon publishing)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    Ok(event.to_string())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn simulate_frost_mailbox_post_signer() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    use rand::thread_rng;&lt;br/&gt;    use std::collections::BTreeMap;&lt;br/&gt;    use frost_secp256k1_tr as frost;&lt;br/&gt;&lt;br/&gt;    // This example simulates a single signer&amp;#39;s role in a ROAST mailbox post workflow.&lt;br/&gt;    // The general workflow is:&lt;br/&gt;    // 1. Coordinator sends a request for signatures (e.g., on a BIP-64MOD proposal).&lt;br/&gt;    // 2. Signers receive the proposal, perform local verification.&lt;br/&gt;    // 3. Each signer generates their signature share and posts it (encrypted) to a&lt;br/&gt;    //    Nostr relay, targeting the coordinator&amp;#39;s mailbox.&lt;br/&gt;    // 4. The coordinator collects enough shares to aggregate the final signature.&lt;br/&gt;&lt;br/&gt;    let mut rng = thread_rng();&lt;br/&gt;    // For this example, we simulate a 2-of-2 threshold for simplicity.&lt;br/&gt;    let (max_signers, min_signers) = (2, 2);&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 1. Key Generation (Simulated Trusted Dealer)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // In a real distributed setup, this would be DKG. Here, a &amp;#34;trusted dealer&amp;#34;&lt;br/&gt;    // generates the shares and public key package.&lt;br/&gt;    let (shares, _pubkey_package) = frost::keys::generate_with_dealer(&lt;br/&gt;        max_signers,&lt;br/&gt;        min_signers,&lt;br/&gt;        frost::keys::IdentifierList::Default,&lt;br/&gt;        &amp;amp;mut rng,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    // For a 2-of-2 scheme, we have two signers. Let&amp;#39;s pick signer 1.&lt;br/&gt;    let signer1_id = frost::Identifier::try_from(1 as u16)?;&lt;br/&gt;    let key_package1: frost::keys::KeyPackage = shares[&amp;amp;signer1_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    let signer2_id = frost::Identifier::try_from(2 as u16)?;&lt;br/&gt;    let key_package2: frost::keys::KeyPackage = shares[&amp;amp;signer2_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    // The message that is to be signed (e.g., a hash of a Git commit or a Nostr event ID).&lt;br/&gt;    let message = b&amp;#34;This is a test message for ROAST mailbox post.&amp;#34;;&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 2. Round 1: Commitment Phase (Signer&amp;#39;s role)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // Each signer generates nonces and commitments.&lt;br/&gt;    let (nonces1, comms1) = frost::round1::commit(key_package1.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    let (nonces2, comms2) = frost::round1::commit(key_package2.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    &lt;br/&gt;    // The coordinator collects these commitments. Here, we simulate by putting them in a BTreeMap.&lt;br/&gt;    let mut session_commitments = BTreeMap::new();&lt;br/&gt;    session_commitments.insert(signer1_id, comms1);&lt;br/&gt;    session_commitments.insert(signer2_id, comms2);&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 3. Signing Package Creation (Coordinator&amp;#39;s role, simulated for context)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // The coordinator combines the collected commitments and the message to be signed&lt;br/&gt;    // into a signing package, which is then sent back to the signers.&lt;br/&gt;    let signing_package = frost::SigningPackage::new(session_commitments, message);&lt;br/&gt;&lt;br/&gt;    // Dummy coordinator public key. In a real scenario, this would be the&lt;br/&gt;    // actual public key of the ROAST coordinator, used for event tagging&lt;br/&gt;    // and encryption (NIP-44).&lt;br/&gt;    let coordinator_pubkey_hex = &amp;#34;0000000000000000000000000000000000000000000000000000000000000001&amp;#34;;&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 4. Create the Signer Event (Signer&amp;#39;s role)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // We demonstrate for signer 1. Signer 2 would perform a similar action.&lt;br/&gt;    let event_json_signer1 = create_signer_event(&lt;br/&gt;        signer1_id,&lt;br/&gt;        &amp;amp;signing_package,&lt;br/&gt;        &amp;amp;nonces1,&lt;br/&gt;        &amp;amp;key_package1,&lt;br/&gt;        coordinator_pubkey_hex,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Generated Nostr Event for Signer 1 Mailbox Post:&lt;br/&gt;{}&amp;#34;, event_json_signer1);&lt;br/&gt;&lt;br/&gt;    // Similarly, Signer 2 would generate their event:&lt;br/&gt;    let event_json_signer2 = create_signer_event(&lt;br/&gt;        signer2_id,&lt;br/&gt;        &amp;amp;signing_package,&lt;br/&gt;        &amp;amp;nonces2,&lt;br/&gt;        &amp;amp;key_package2,&lt;br/&gt;        coordinator_pubkey_hex,&lt;br/&gt;    )?;&lt;br/&gt;    println!(&amp;#34;Generated Nostr Event for Signer 2 Mailbox Post:&lt;br/&gt;{}&amp;#34;, event_json_signer2);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}
    </content>
    <updated>2026-04-05T02:57:42&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdezap0gcysk63jxv0s8n2lv0nxkf9mf33m00mjvwuvnlpnygq5vqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp4hmx0</id>
    
      <title type="html">#[cfg(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;))] ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdezap0gcysk63jxv0s8n2lv0nxkf9mf33m00mjvwuvnlpnygq5vqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp4hmx0" />
    <content type="html">
      #[cfg(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;))]&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() {&lt;br/&gt;    use std::fs;&lt;br/&gt;    use std::path::PathBuf;&lt;br/&gt;&lt;br/&gt;    let manifest_dir = PathBuf::from(std::env::var(&amp;#34;CARGO_MANIFEST_DIR&amp;#34;).unwrap());&lt;br/&gt;    let crate_src_path = manifest_dir.join(&amp;#34;src&amp;#34;).join(&amp;#34;online_relays_gps.csv&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Only download if the file doesn&amp;#39;t exist or is empty&lt;br/&gt;    if !crate_src_path.exists() || fs::metadata(&amp;amp;crate_src_path).map(|m| m.len() == 0).unwrap_or(true) {&lt;br/&gt;        println!(&amp;#34;cargo:warning=Downloading online_relays_gps.csv...&amp;#34;);&lt;br/&gt;        let url = &amp;#34;&lt;a href=&#34;https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv&amp;#34&#34;&gt;https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv&amp;#34&lt;/a&gt;;;&lt;br/&gt;        match reqwest::get(url).await {&lt;br/&gt;            Ok(response) =&amp;gt; {&lt;br/&gt;                if response.status().is_success() {&lt;br/&gt;                    match response.text().await {&lt;br/&gt;                        Ok(content) =&amp;gt; {&lt;br/&gt;                            fs::write(&amp;amp;crate_src_path, content).expect(&amp;#34;Unable to write online_relays_gps.csv&amp;#34;);&lt;br/&gt;                            println!(&amp;#34;cargo:warning=Successfully downloaded online_relays_gps.csv to {:?}&amp;#34;, crate_src_path);&lt;br/&gt;                        },&lt;br/&gt;                        Err(e) =&amp;gt; {&lt;br/&gt;                            println!(&amp;#34;cargo:warning=Failed to get text from response: {}&amp;#34;, e);&lt;br/&gt;                        }&lt;br/&gt;                    }&lt;br/&gt;                } else {&lt;br/&gt;                    println!(&amp;#34;cargo:warning=Failed to download online_relays_gps.csv: HTTP status {}&amp;#34;, response.status());&lt;br/&gt;                }&lt;br/&gt;            },&lt;br/&gt;            Err(e) =&amp;gt; {&lt;br/&gt;                println!(&amp;#34;cargo:warning=Failed to fetch online_relays_gps.csv: {}&amp;#34;, e);&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(not(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;)))]&lt;br/&gt;fn main() {&lt;br/&gt;    // Placeholder for when the nostr feature is not enabled or in debug mode&lt;br/&gt;    println!(&amp;#34;cargo:warning=Skipping online_relays_gps.csv download (nostr feature not enabled or debug mode)&amp;#34;);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:57:31&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqwctxg949m67sjg6p0aavfzpdpwr6yu5zcjuzw9j2rrad00v0a5gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsc2efet</id>
    
      <title type="html">[package] name = &amp;#34;get_file_hash_core&amp;#34; version = { ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqwctxg949m67sjg6p0aavfzpdpwr6yu5zcjuzw9j2rrad00v0a5gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsc2efet" />
    <content type="html">
      [package]&lt;br/&gt;name = &amp;#34;get_file_hash_core&amp;#34;&lt;br/&gt;version = { workspace = true }&lt;br/&gt;edition = { workspace = true }&lt;br/&gt;description = { workspace = true }&lt;br/&gt;license = { workspace = true }&lt;br/&gt;documentation = { workspace = true }&lt;br/&gt;homepage = { workspace = true }&lt;br/&gt;repository = { workspace = true }&lt;br/&gt;authors = { workspace = true }&lt;br/&gt;&lt;br/&gt;[features]&lt;br/&gt;nostr = [&amp;#34;dep:nostr&amp;#34;, &amp;#34;dep:nostr-sdk&amp;#34;, &amp;#34;dep:serde_json&amp;#34;, &amp;#34;dep:sha2&amp;#34;, &amp;#34;dep:hex&amp;#34;, &amp;#34;dep:reqwest&amp;#34;, &amp;#34;dep:tokio&amp;#34;, &amp;#34;dep:csv&amp;#34;, &amp;#34;dep:url&amp;#34;, &amp;#34;dep:frost-secp256k1-tr&amp;#34;, &amp;#34;dep:rand&amp;#34;]&lt;br/&gt;frost = [&amp;#34;dep:nostr&amp;#34;, &amp;#34;dep:nostr-sdk&amp;#34;, &amp;#34;dep:serde_json&amp;#34;, &amp;#34;dep:sha2&amp;#34;, &amp;#34;dep:hex&amp;#34;, &amp;#34;dep:reqwest&amp;#34;, &amp;#34;dep:tokio&amp;#34;, &amp;#34;dep:csv&amp;#34;, &amp;#34;dep:url&amp;#34;, &amp;#34;dep:frost-secp256k1-tr&amp;#34;, &amp;#34;dep:rand&amp;#34;]&lt;br/&gt;&lt;br/&gt;[dependencies]&lt;br/&gt;sha2 = { workspace = true, optional = true }&lt;br/&gt;nostr = { workspace = true, optional = true }&lt;br/&gt;serde_json = { workspace = true, optional = true }&lt;br/&gt;nostr-sdk = { workspace = true, optional = true }&lt;br/&gt;hex = { workspace = true, optional = true }&lt;br/&gt;csv = { workspace = true, optional = true }&lt;br/&gt;url = { workspace = true, optional = true }&lt;br/&gt;frost-secp256k1-tr = { workspace = true, optional = true }&lt;br/&gt;rand = { workspace = true, optional = true }&lt;br/&gt;&lt;br/&gt;[dev-dependencies]&lt;br/&gt;sha2 = { workspace = true }&lt;br/&gt;tempfile = { workspace = true }&lt;br/&gt;nostr = { workspace = true }&lt;br/&gt;nostr-sdk = { workspace = true }&lt;br/&gt;serde_json = { workspace = true }&lt;br/&gt;hex = { workspace = true }&lt;br/&gt;tokio = { workspace = true, features = [&amp;#34;macros&amp;#34;, &amp;#34;rt-multi-thread&amp;#34;] }&lt;br/&gt;csv = { workspace = true }&lt;br/&gt;url = { workspace = true }&lt;br/&gt;frost-secp256k1-tr = { workspace = true }&lt;br/&gt;serial_test = { workspace = true, features = [&amp;#34;test_logging&amp;#34;] }&lt;br/&gt;log = { workspace = true }&lt;br/&gt;&lt;br/&gt;[build-dependencies]&lt;br/&gt;reqwest = { workspace = true, features = [&amp;#34;json&amp;#34;], optional = true }&lt;br/&gt;tokio = { workspace = true, features = [&amp;#34;macros&amp;#34;, &amp;#34;rt-multi-thread&amp;#34;], optional = true }&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:57:20&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstjl77ugs66wajv2ycm90wrha5qfcxanya3s0z2v248h0uuhra3gszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs7yhdej</id>
    
      <title type="html">//! A simple command-line tool that calculates and displays the ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstjl77ugs66wajv2ycm90wrha5qfcxanya3s0z2v248h0uuhra3gszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs7yhdej" />
    <content type="html">
      //! A simple command-line tool that calculates and displays the SHA-256 hash of&lt;br/&gt;//! its own source file.&lt;br/&gt;//!&lt;br/&gt;//! This utility demonstrates how to use the `get_file_hash!` macro to obtain&lt;br/&gt;//! the hash of a specified file at compile time and incorporate it into runtime&lt;br/&gt;//! logic.&lt;br/&gt;use get_file_hash::{BUILD_HASH, CARGO_TOML_HASH, LIB_HASH};&lt;br/&gt;use get_file_hash_core::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART0: &amp;amp;str = r##&amp;#34;# `get_file_hash` macro&lt;br/&gt;&lt;br/&gt;This project provides a Rust procedural macro, `get_file_hash!`, designed to compute the SHA-256 hash of a specified file at compile time. This hash is then embedded directly into your compiled executable. This feature is invaluable for:&lt;br/&gt;&lt;br/&gt;*   **Integrity Verification:** Ensuring the deployed code hasn&amp;#39;t been tampered with.&lt;br/&gt;*   **Versioning:** Embedding a unique identifier linked to the exact source code version.&lt;br/&gt;*   **Cache Busting:** Generating unique names for assets based on their content.&lt;br/&gt;&lt;br/&gt;## Project Structure&lt;br/&gt;&lt;br/&gt;*   `get_file_hash_core`: A foundational crate containing the `get_file_hash!` macro definition.&lt;br/&gt;*   `get_file_hash`: The main library crate that re-exports the macro.&lt;br/&gt;*   `src/bin/get_file_hash.rs`: An example executable demonstrating the macro&amp;#39;s usage by hashing its own source file and updating this `README.md`.&lt;br/&gt;*   `build.rs`: A build script that also utilizes the `get_file_hash!` macro to hash `Cargo.toml` during the build process.&lt;br/&gt;&lt;br/&gt;## Usage of `get_file_hash!` Macro&lt;br/&gt;&lt;br/&gt;To use the `get_file_hash!` macro, ensure you have `get_file_hash` (or `get_file_hash_core` for direct usage) as a dependency in your `Cargo.toml`.&lt;br/&gt;&lt;br/&gt;### Example&lt;br/&gt;&lt;br/&gt;```rust&lt;br/&gt;use get_file_hash::get_file_hash;&lt;br/&gt;use get_file_hash::CARGO_TOML_HASH;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn main() {&lt;br/&gt;    // The macro resolves the path relative to CARGO_MANIFEST_DIR&lt;br/&gt;    let readme_hash = get_file_hash!(&amp;#34;src/bin/readme.rs&amp;#34;);&lt;br/&gt;    let lib_hash = get_file_hash!(&amp;#34;src/lib.rs&amp;#34;);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/lib.rs is: {}&amp;#34;, lib_hash);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/bin/readme.rs is: {}&amp;#34;, readme_hash);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of Cargo.toml is: {}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;}&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;##;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART1: &amp;amp;str = r&amp;#34;## Release&lt;br/&gt;## [`README.md`](./README.md)&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --bin readme &amp;gt; README.md&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;## [`src/bin/readme.rs`](src/bin/readme.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/bin/readme.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART2: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`build.rs`](build.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `build.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART3: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`Cargo.toml`](Cargo.toml)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `Cargo.toml`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART4: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`src/lib.rs`](src/lib.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/lib.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART_NIP34: &amp;amp;str = r&amp;#34;## NIP-34 Integration: Git Repository Events on Nostr&lt;br/&gt;&lt;br/&gt;This library provides a set of powerful macros and functions for integrating Git repository events with the Nostr protocol, adhering to the [NIP-34: Git Repositories on Nostr](&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/34.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/34.md&lt;/a&gt;) specification.&lt;br/&gt;&lt;br/&gt;These tools allow you to publish various Git-related events to Nostr relays, enabling decentralized tracking and collaboration for your code repositories.&lt;br/&gt;&lt;br/&gt;### Available NIP-34 Macros&lt;br/&gt;&lt;br/&gt;Each macro provides a convenient way to publish specific NIP-34 event kinds:&lt;br/&gt;&lt;br/&gt;*   [`repository_announcement!`](#repository_announcement)&lt;br/&gt;    *   Publishes a `Repository Announcement` event (Kind 30617) to announce a new or updated Git repository.&lt;br/&gt;*   [`publish_patch!`](#publish_patch)&lt;br/&gt;    *   Publishes a `Patch` event (Kind 1617) containing a Git patch (diff) for a specific commit.&lt;br/&gt;*   [`publish_pull_request!`](#publish_pull_request)&lt;br/&gt;    *   Publishes a `Pull Request` event (Kind 1618) to propose changes and facilitate code review.&lt;br/&gt;*   [`publish_pr_update!`](#publish_pr_update)&lt;br/&gt;    *   Publishes a `Pull Request Update` event (Kind 1619) to update an existing pull request.&lt;br/&gt;*   [`publish_repository_state!`](#publish_repository_state)&lt;br/&gt;    *   Publishes a `Repository State` event (Kind 1620) to announce the current state of a branch (e.g., its latest commit).&lt;br/&gt;*   [`publish_issue!`](#publish_issue)&lt;br/&gt;    *   Publishes an `Issue` event (Kind 1621) to report bugs, request features, or track tasks.&lt;br/&gt;&lt;br/&gt;### Running NIP-34 Examples&lt;br/&gt;&lt;br/&gt;To see these macros in action, navigate to the `examples/` directory and run each example individually with the `nostr` feature enabled:&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --example repository_announcement --features nostr&lt;br/&gt;cargo run --example publish_patch --features nostr&lt;br/&gt;cargo run --example publish_pull_request --features nostr&lt;br/&gt;cargo run --example publish_pr_update --features nostr&lt;br/&gt;cargo run --example publish_repository_state --features nostr&lt;br/&gt;cargo run --example publish_issue --features nostr&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;/// The main entry point of the application.&lt;br/&gt;///&lt;br/&gt;/// This function calculates the SHA-256 hash of the `get_file_hash.rs` source&lt;br/&gt;/// file using a custom procedural macro and then prints the hash to the&lt;br/&gt;/// console. It also includes a basic integrity verification check.&lt;br/&gt;fn main() {&lt;br/&gt;    // Calculate the SHA-256 hash of the current file (`readme.rs`) at&lt;br/&gt;    // compile time. The `get_file_hash!` macro reads the file content and&lt;br/&gt;    // computes its hash.&lt;br/&gt;    let self_hash = get_file_hash!(&amp;#34;readme.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let status_message = if self_hash.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    let build_message = if BUILD_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;    let cargo_message = if CARGO_TOML_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;    let lib_message = if LIB_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    print!(&amp;#34;{}{}{}&amp;#34;, README_TEMPLATE_PART0, README_TEMPLATE_PART1, README_TEMPLATE_PART_NIP34);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, self_hash);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, status_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART2);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, BUILD_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, build_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART3);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, cargo_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART4);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, LIB_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, lib_message);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:57:07&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsg594w9y7vrydzjepadavwwzgfjguuuws6t270qaawscnvr85u4uqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs82em5a</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsg594w9y7vrydzjepadavwwzgfjguuuws6t270qaawscnvr85u4uqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs82em5a" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Command line interface module&lt;br/&gt;use n34::cli;&lt;br/&gt;/// N34 errors&lt;br/&gt;use n34::error;&lt;br/&gt;/// Nostr utils module&lt;br/&gt;use n34::nostr_utils;&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    process::ExitCode,&lt;br/&gt;    sync::atomic::{AtomicBool, Ordering},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Parser;&lt;br/&gt;use clap_verbosity_flag::Verbosity;&lt;br/&gt;use tracing::Level;&lt;br/&gt;use tracing_subscriber::{Layer, filter, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;use self::cli::Cli;&lt;br/&gt;&lt;br/&gt;/// Whether the editor is currently open. Prevents logging while the editor is&lt;br/&gt;/// open.&lt;br/&gt;static EDITOR_OPEN: AtomicBool = AtomicBool::new(false);&lt;br/&gt;&lt;br/&gt;/// Configures the logging level based on the provided verbosity.&lt;br/&gt;///&lt;br/&gt;/// When verbosity is set to TRACE, includes file and line numbers in logs.&lt;br/&gt;fn set_log_level(verbosity: Verbosity) {&lt;br/&gt;    let is_trace = verbosity&lt;br/&gt;        .tracing_level()&lt;br/&gt;        .is_some_and(|l| l == tracing::Level::TRACE);&lt;br/&gt;&lt;br/&gt;    let logs_filter = filter::dynamic_filter_fn(move |m, _| {&lt;br/&gt;        // Disable all logs while editor is open&lt;br/&gt;        verbosity.tracing_level().unwrap_or(Level::ERROR) &amp;gt;= *m.level()&lt;br/&gt;            &amp;amp;&amp;amp; !EDITOR_OPEN.load(Ordering::Relaxed)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    let logs_layer = tracing_subscriber::fmt::layer()&lt;br/&gt;        .with_file(is_trace)&lt;br/&gt;        .with_line_number(is_trace)&lt;br/&gt;        .without_time();&lt;br/&gt;    let subscriber = tracing_subscriber::registry().with(logs_layer.with_filter(logs_filter));&lt;br/&gt;    tracing::subscriber::set_global_default(subscriber).ok();&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() -&amp;gt; ExitCode {&lt;br/&gt;    let cli = match cli::post_cli(Cli::parse()) {&lt;br/&gt;        Ok(cli) =&amp;gt; cli,&lt;br/&gt;        Err(err) =&amp;gt; {&lt;br/&gt;            eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;            return ExitCode::FAILURE;&lt;br/&gt;        }&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    set_log_level(cli.verbosity);&lt;br/&gt;&lt;br/&gt;    if let Err(err) = cli.run().await {&lt;br/&gt;        tracing::error!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;        return err.exit_code();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    ExitCode::SUCCESS&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:56:53&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqx2crpy9dl4sndaap5wquakh0j57axl9vtuuv7hlcev4gjfx8nhszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs9eey0x</id>
    
      <title type="html">// n34-relay - A nostr GRASP relay implementation // Copyright ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqx2crpy9dl4sndaap5wquakh0j57axl9vtuuv7hlcev4gjfx8nhszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs9eey0x" />
    <content type="html">
      // n34-relay - A nostr GRASP relay implementation&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU Affero General Public License as published&lt;br/&gt;// by the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU Affero General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU Affero General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/agpl-3.0&amp;gt&#34;&gt;https://gnu.org/licenses/agpl-3.0&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{net::SocketAddr, process::ExitCode, sync::Arc};&lt;br/&gt;&lt;br/&gt;use axum::Extension;&lt;br/&gt;use hyper::{Method, header};&lt;br/&gt;use tokio::signal;&lt;br/&gt;use tower_http::{&lt;br/&gt;    cors,&lt;br/&gt;    decompression::RequestDecompressionLayer,&lt;br/&gt;    trace::{DefaultMakeSpan, TraceLayer},&lt;br/&gt;};&lt;br/&gt;use tracing::level_filters::LevelFilter;&lt;br/&gt;use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;/// Relay endpoints&lt;br/&gt;use n34_relay::endpoints;&lt;br/&gt;/// Relay errors.&lt;br/&gt;use n34_relay::errors;&lt;br/&gt;/// Extension traits&lt;br/&gt;use n34_relay::ext_traits;&lt;br/&gt;/// GRASP git server&lt;br/&gt;use n34_relay::git_server;&lt;br/&gt;/// Relay pathes.&lt;br/&gt;use n34_relay::pathes;&lt;br/&gt;/// Raw axum websocket&lt;br/&gt;use n34_relay::raw_websocket;&lt;br/&gt;/// Our relay.&lt;br/&gt;use n34_relay::relay;&lt;br/&gt;/// Relay configuration.&lt;br/&gt;use n34_relay::relay_config;&lt;br/&gt;/// Router state&lt;br/&gt;use n34_relay::router_state;&lt;br/&gt;/// Some useful utils.&lt;br/&gt;use n34_relay::utils;&lt;br/&gt;&lt;br/&gt;use self::{errors::RelayResult, relay_config::RelayConfig, router_state::RouterState};&lt;br/&gt;&lt;br/&gt;/// Sets up default logging with two outputs, stderr and a log file.&lt;br/&gt;///&lt;br/&gt;/// Log level for stderr is controlled by `RUST_LOG` environment variable,&lt;br/&gt;/// defaults to `ERROR`. The log file always uses `TRACE` level.&lt;br/&gt;fn setup_logs() -&amp;gt; errors::RelayResult&amp;lt;()&amp;gt; {&lt;br/&gt;    tracing::subscriber::set_global_default(&lt;br/&gt;        tracing_subscriber::registry()&lt;br/&gt;            .with(&lt;br/&gt;                tracing_subscriber::fmt::layer()&lt;br/&gt;                    .with_ansi(true)&lt;br/&gt;                    .with_writer(std::io::stderr)&lt;br/&gt;                    .without_time()&lt;br/&gt;                    .with_filter(EnvFilter::from_default_env()),&lt;br/&gt;            )&lt;br/&gt;            .with(&lt;br/&gt;                tracing_subscriber::fmt::layer()&lt;br/&gt;                    .with_ansi(false)&lt;br/&gt;                    .with_writer(utils::logs_file()?)&lt;br/&gt;                    .with_file(false)&lt;br/&gt;                    .with_line_number(false)&lt;br/&gt;                    .with_filter(LevelFilter::TRACE),&lt;br/&gt;            ),&lt;br/&gt;    )&lt;br/&gt;    .ok();&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;async fn shutdown_signal() {&lt;br/&gt;    let ctrl_c = async {&lt;br/&gt;        signal::ctrl_c()&lt;br/&gt;            .await&lt;br/&gt;            .expect(&amp;#34;Failed to install CTRL&#43;C handler&amp;#34;);&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    #[cfg(unix)]&lt;br/&gt;    let terminate = async {&lt;br/&gt;        use tokio::signal::unix::SignalKind;&lt;br/&gt;&lt;br/&gt;        signal::unix::signal(SignalKind::terminate())&lt;br/&gt;            .expect(&amp;#34;Failed to create SIGTERM handler&amp;#34;)&lt;br/&gt;            .recv()&lt;br/&gt;            .await;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    #[cfg(not(unix))]&lt;br/&gt;    let terminate = std::future::pending::&amp;lt;()&amp;gt;();&lt;br/&gt;&lt;br/&gt;    tokio::select! {&lt;br/&gt;        _ = ctrl_c =&amp;gt; {},&lt;br/&gt;        _ = terminate =&amp;gt; {},&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;async fn try_main() -&amp;gt; RelayResult&amp;lt;()&amp;gt; {&lt;br/&gt;    setup_logs()?;&lt;br/&gt;    let config = Arc::new(RelayConfig::reload()?);&lt;br/&gt;    let relay_db = config.get_relay_db().await?;&lt;br/&gt;    let n34_relay = Arc::new(relay::build_relay(Arc::clone(&amp;amp;config), Arc::clone(&amp;amp;relay_db)).await);&lt;br/&gt;    let addr = SocketAddr::new(config.net.ip, config.net.port);&lt;br/&gt;&lt;br/&gt;    tracing::debug!(&amp;#34;Running relay with configuration: {config:#?}&amp;#34;);&lt;br/&gt;    tracing::info!(&amp;#34;Relay is running at `{}`&amp;#34;, n34_relay.url().await);&lt;br/&gt;&lt;br/&gt;    let mut app = axum::Router::new()&lt;br/&gt;        // main handler. GET and POST&lt;br/&gt;        .route(&amp;#34;/&amp;#34;, axum::routing::get(endpoints::main_handler).post(endpoints::main_handler));&lt;br/&gt;&lt;br/&gt;    if config.grasp.enable {&lt;br/&gt;        tracing::info!(&amp;#34;Git server is running&amp;#34;);&lt;br/&gt;        app = app.merge(git_server::router(&amp;amp;config));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    app = app&lt;br/&gt;        // enable cross-origin access&lt;br/&gt;        .route(&lt;br/&gt;            &amp;#34;/&amp;#34;,&lt;br/&gt;            axum::routing::options(|| async { hyper::StatusCode::NO_CONTENT }),&lt;br/&gt;        )&lt;br/&gt;        .layer(&lt;br/&gt;            cors::CorsLayer::new()&lt;br/&gt;                .allow_origin(cors::Any)&lt;br/&gt;                .allow_methods([Method::GET, Method::POST])&lt;br/&gt;                .allow_headers([header::CONTENT_TYPE]),&lt;br/&gt;        )&lt;br/&gt;        .layer(&lt;br/&gt;            TraceLayer::new_for_http()&lt;br/&gt;                .make_span_with(DefaultMakeSpan::default().include_headers(true)),&lt;br/&gt;        )&lt;br/&gt;        .layer(RequestDecompressionLayer::new())&lt;br/&gt;        .layer(Extension(Arc::new(RouterState::new(&lt;br/&gt;            config, n34_relay, relay_db,&lt;br/&gt;        ))));&lt;br/&gt;&lt;br/&gt;    axum::serve(&lt;br/&gt;        tokio::net::TcpListener::bind(addr).await?,&lt;br/&gt;        app.into_make_service_with_connect_info::&amp;lt;SocketAddr&amp;gt;(),&lt;br/&gt;    )&lt;br/&gt;    .with_graceful_shutdown(shutdown_signal())&lt;br/&gt;    .await?;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() -&amp;gt; ExitCode {&lt;br/&gt;    if let Err(err) = try_main().await {&lt;br/&gt;        eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;        return ExitCode::FAILURE;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    tracing::info!(&amp;#34;Exited gracefully without any errors&amp;#34;);&lt;br/&gt;    ExitCode::SUCCESS&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:56:42&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsd2xq3y4zr2haulayewrlw0355qlhkcduq928cdvp7mfcnn26t4fczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsezg3zx</id>
    
      <title type="html">//! A simple command-line tool that calculates and displays the ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsd2xq3y4zr2haulayewrlw0355qlhkcduq928cdvp7mfcnn26t4fczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsezg3zx" />
    <content type="html">
      //! A simple command-line tool that calculates and displays the SHA-256 hash of&lt;br/&gt;//! its own source file.&lt;br/&gt;//!&lt;br/&gt;//! This utility demonstrates how to use the `get_file_hash!` macro to obtain&lt;br/&gt;//! the hash of a specified file at compile time and incorporate it into runtime&lt;br/&gt;//! logic.&lt;br/&gt;&lt;br/&gt;use get_file_hash_core::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART1: &amp;amp;str = r##&amp;#34;# `get_file_hash` macro&lt;br/&gt;&lt;br/&gt;This project provides a Rust procedural macro, `get_file_hash!`, designed to compute the SHA-256 hash of a specified file at compile time. This hash is then embedded directly into your compiled executable. This feature is invaluable for:&lt;br/&gt;&lt;br/&gt;*   **Integrity Verification:** Ensuring the deployed code hasn&amp;#39;t been tampered with.&lt;br/&gt;*   **Versioning:** Embedding a unique identifier linked to the exact source code version.&lt;br/&gt;*   **Cache Busting:** Generating unique names for assets based on their content.&lt;br/&gt;&lt;br/&gt;## Project Structure&lt;br/&gt;&lt;br/&gt;*   `get_file_hash_core`: A foundational crate containing the `get_file_hash!` macro definition.&lt;br/&gt;*   `get_file_hash`: The main library crate that re-exports the macro.&lt;br/&gt;*   `src/bin/get_file_hash.rs`: An example executable demonstrating the macro&amp;#39;s usage by hashing its own source file and updating this `README.md`.&lt;br/&gt;*   `build.rs`: A build script that also utilizes the `get_file_hash!` macro to hash `Cargo.toml` during the build process.&lt;br/&gt;&lt;br/&gt;## Usage of `get_file_hash!` Macro&lt;br/&gt;&lt;br/&gt;To use the `get_file_hash!` macro, ensure you have `get_file_hash` (or `get_file_hash_core` for direct usage) as a dependency in your `Cargo.toml`.&lt;br/&gt;&lt;br/&gt;### Example&lt;br/&gt;&lt;br/&gt;```rust&lt;br/&gt;use get_file_hash::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn main() {&lt;br/&gt;    // The macro resolves the path relative to CARGO_MANIFEST_DIR&lt;br/&gt;    let file_hash = get_file_hash!(&amp;#34;src/lib.rs&amp;#34;);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/lib.rs is: {}&amp;#34;, file_hash);&lt;br/&gt;}&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;##;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART2: &amp;amp;str = r&amp;#34;## Setup and Building&lt;br/&gt;&lt;br/&gt;1.  **Clone the repository:**&lt;br/&gt;    ```bash&lt;br/&gt;    git clone &amp;lt;repository-url&amp;gt;&lt;br/&gt;    cd &amp;lt;repository-name&amp;gt;&lt;br/&gt;    ```&lt;br/&gt;2.  **Build the project:**&lt;br/&gt;    ```bash&lt;br/&gt;    cargo build&lt;br/&gt;    ```&lt;br/&gt;    During the build, `build.rs` will execute and print the hash of `Cargo.toml`.&lt;br/&gt;3.  **Run the example executable:**&lt;br/&gt;    ```bash&lt;br/&gt;    cargo run --bin get_file_hash&lt;br/&gt;    ```&lt;br/&gt;    This will print the hash of `src/bin/get_file_hash.rs` to your console.&lt;br/&gt;&lt;br/&gt;## Updating this `README.md`&lt;br/&gt;&lt;br/&gt;The hash information in this `README.md` is automatically generated by running the example executable.&lt;br/&gt;To update it, execute:&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --bin get_file_hash &amp;gt; README.md&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;## Current File Hash Information (of `src/bin/get_file_hash.rs`)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/bin/get_file_hash.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;/// The main entry point of the application.&lt;br/&gt;///&lt;br/&gt;/// This function calculates the SHA-256 hash of the `get_file_hash.rs` source&lt;br/&gt;/// file using a custom procedural macro and then prints the hash to the&lt;br/&gt;/// console. It also includes a basic integrity verification check.&lt;br/&gt;fn main() {&lt;br/&gt;    // Calculate the SHA-256 hash of the current file (`get_file_hash.rs`) at&lt;br/&gt;    // compile time. The `get_file_hash!` macro reads the file content and&lt;br/&gt;    // computes its hash.&lt;br/&gt;    let self_hash = get_file_hash!(&amp;#34;get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let status_message = if self_hash.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    print!(&amp;#34;{}{}&amp;#34;, README_TEMPLATE_PART1, README_TEMPLATE_PART2);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, self_hash);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, status_message);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:56:31&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspgzv6xhucx09j9kkcd8q43pdgsddk8uu6gekxyv4ku6555hgjjngzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszvyhe6</id>
    
      <title type="html">&amp;lt;?xml version=&amp;#39;1.0&amp;#39; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspgzv6xhucx09j9kkcd8q43pdgsddk8uu6gekxyv4ku6555hgjjngzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszvyhe6" />
    <content type="html">
      &amp;lt;?xml version=&amp;#39;1.0&amp;#39; encoding=&amp;#39;windows-1252&amp;#39;?&amp;gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Copyright (C) 2017 Christopher R. Field.&lt;br/&gt;&lt;br/&gt;  Licensed under the Apache License, Version 2.0 (the &amp;#34;License&amp;#34;);&lt;br/&gt;  you may not use this file except in compliance with the License.&lt;br/&gt;  You may obtain a copy of the License at&lt;br/&gt;&lt;br/&gt;  &lt;a href=&#34;http://www.apache.org/licenses/LICENSE-2.0&#34;&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;  Unless required by applicable law or agreed to in writing, software&lt;br/&gt;  distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,&lt;br/&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br/&gt;  See the License for the specific language governing permissions and&lt;br/&gt;  limitations under the License.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  The &amp;#34;cargo wix&amp;#34; subcommand provides a variety of predefined variables available&lt;br/&gt;  for customization of this template. The values for each variable are set at&lt;br/&gt;  installer creation time. The following variables are available:&lt;br/&gt;&lt;br/&gt;  TargetTriple      = The rustc target triple name.&lt;br/&gt;  TargetEnv         = The rustc target environment. This is typically either&lt;br/&gt;                      &amp;#34;msvc&amp;#34; or &amp;#34;gnu&amp;#34; depending on the toolchain downloaded and&lt;br/&gt;                      installed.&lt;br/&gt;  TargetVendor      = The rustc target vendor. This is typically &amp;#34;pc&amp;#34;, but Rust&lt;br/&gt;                      does support other vendors, like &amp;#34;uwp&amp;#34;.&lt;br/&gt;  CargoTargetBinDir = The complete path to the directory containing the&lt;br/&gt;                      binaries (exes) to include. The default would be&lt;br/&gt;                      &amp;#34;target\release\&amp;#34;. If an explicit rustc target triple is&lt;br/&gt;                      used, i.e. cross-compiling, then the default path would&lt;br/&gt;                      be &amp;#34;target\&amp;lt;CARGO_TARGET&amp;gt;\&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34;,&lt;br/&gt;                      where &amp;#34;&amp;lt;CARGO_TARGET&amp;gt;&amp;#34; is replaced with the &amp;#34;CargoTarget&amp;#34;&lt;br/&gt;                      variable value and &amp;#34;&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34; is replaced with the&lt;br/&gt;                      value from the &amp;#34;CargoProfile&amp;#34; variable. This can also&lt;br/&gt;                      be overridden manually with the &amp;#34;target-bin-dir&amp;#34; flag.&lt;br/&gt;  CargoTargetDir    = The path to the directory for the build artifacts, i.e.&lt;br/&gt;                      &amp;#34;target&amp;#34;.&lt;br/&gt;  CargoProfile      = The cargo profile used to build the binaries&lt;br/&gt;                      (usually &amp;#34;debug&amp;#34; or &amp;#34;release&amp;#34;).&lt;br/&gt;  Version           = The version for the installer. The default is the&lt;br/&gt;                      &amp;#34;Major.Minor.Fix&amp;#34; semantic versioning number of the Rust&lt;br/&gt;                      package.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Please do not remove these pre-processor If-Else blocks. These are used with&lt;br/&gt;  the `cargo wix` subcommand to automatically determine the installation&lt;br/&gt;  destination for 32-bit versus 64-bit installers. Removal of these lines will&lt;br/&gt;  cause installation errors.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&amp;lt;?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFiles64Folder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?else ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFilesFolder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?endif ?&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;Wix xmlns=&amp;#39;&lt;a href=&#34;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&#34;&gt;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&lt;/a&gt;;&lt;br/&gt;&lt;br/&gt;    &amp;lt;Product&lt;br/&gt;        Id=&amp;#39;*&amp;#39;&lt;br/&gt;        Name=&amp;#39;n34&amp;#39;&lt;br/&gt;        UpgradeCode=&amp;#39;5E8926D0-4A18-48E9-A070-AFB0F698F04C&amp;#39;&lt;br/&gt;        Manufacturer=&amp;#39;Awiteb&amp;#39;&lt;br/&gt;        Language=&amp;#39;1033&amp;#39;&lt;br/&gt;        Codepage=&amp;#39;1252&amp;#39;&lt;br/&gt;        Version=&amp;#39;$(var.Version)&amp;#39;&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Package Id=&amp;#39;*&amp;#39;&lt;br/&gt;            Keywords=&amp;#39;Installer&amp;#39;&lt;br/&gt;            Description=&amp;#39;A CLI to interact with NIP-34 and other stuff related to code in Nostr&amp;#39;&lt;br/&gt;            Manufacturer=&amp;#39;Awiteb&amp;#39;&lt;br/&gt;            InstallerVersion=&amp;#39;450&amp;#39;&lt;br/&gt;            Languages=&amp;#39;1033&amp;#39;&lt;br/&gt;            Compressed=&amp;#39;yes&amp;#39;&lt;br/&gt;            InstallScope=&amp;#39;perMachine&amp;#39;&lt;br/&gt;            SummaryCodepage=&amp;#39;1252&amp;#39;&lt;br/&gt;            /&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;MajorUpgrade&lt;br/&gt;            Schedule=&amp;#39;afterInstallInitialize&amp;#39;&lt;br/&gt;            DowngradeErrorMessage=&amp;#39;A newer version of [ProductName] is already installed. Setup will now exit.&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Media Id=&amp;#39;1&amp;#39; Cabinet=&amp;#39;media1.cab&amp;#39; EmbedCab=&amp;#39;yes&amp;#39; DiskPrompt=&amp;#39;CD-ROM #1&amp;#39;/&amp;gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;DiskPrompt&amp;#39; Value=&amp;#39;n34 Installation&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Directory Id=&amp;#39;TARGETDIR&amp;#39; Name=&amp;#39;SourceDir&amp;#39;&amp;gt;&lt;br/&gt;            &amp;lt;Directory Id=&amp;#39;$(var.PlatformProgramFilesFolder)&amp;#39; Name=&amp;#39;PFiles&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;Directory Id=&amp;#39;APPLICATIONFOLDER&amp;#39; Name=&amp;#39;n34&amp;#39;&amp;gt;&lt;br/&gt;                    &lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                      Enabling the license sidecar file in the installer is a four step process:&lt;br/&gt;&lt;br/&gt;                      1. Uncomment the `Component` tag and its contents.&lt;br/&gt;                      2. Change the value for the `Source` attribute in the `File` tag to a path&lt;br/&gt;                         to the file that should be included as the license sidecar file. The path&lt;br/&gt;                         can, and probably should be, relative to this file.&lt;br/&gt;                      3. Change the value for the `Name` attribute in the `File` tag to the&lt;br/&gt;                         desired name for the file when it is installed alongside the `bin` folder&lt;br/&gt;                         in the installation directory. This can be omitted if the desired name is&lt;br/&gt;                         the same as the file name.&lt;br/&gt;                      4. Uncomment the `ComponentRef` tag with the Id attribute value of &amp;#34;License&amp;#34;&lt;br/&gt;                         further down in this file.&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                    &amp;lt;Component Id=&amp;#39;License&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;File Id=&amp;#39;LicenseFile&amp;#39; Name=&amp;#39;ChangeMe&amp;#39; DiskId=&amp;#39;1&amp;#39; Source=&amp;#39;C:\Path\To\File&amp;#39; KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                    &amp;lt;/Component&amp;gt;&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;&lt;br/&gt;                    &amp;lt;Directory Id=&amp;#39;Bin&amp;#39; Name=&amp;#39;bin&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;Path&amp;#39; Guid=&amp;#39;7A2E5F6A-FC23-498B-AEC1-5FA63678FBFD&amp;#39; KeyPath=&amp;#39;yes&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;Environment&lt;br/&gt;                                Id=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Name=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Value=&amp;#39;[Bin]&amp;#39;&lt;br/&gt;                                Permanent=&amp;#39;no&amp;#39;&lt;br/&gt;                                Part=&amp;#39;last&amp;#39;&lt;br/&gt;                                Action=&amp;#39;set&amp;#39;&lt;br/&gt;                                System=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary0&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe0&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34-cli.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34-cli.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                    &amp;lt;/Directory&amp;gt;&lt;br/&gt;                &amp;lt;/Directory&amp;gt;&lt;br/&gt;            &amp;lt;/Directory&amp;gt;&lt;br/&gt;        &amp;lt;/Directory&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Feature&lt;br/&gt;            Id=&amp;#39;Binaries&amp;#39;&lt;br/&gt;            Title=&amp;#39;Application&amp;#39;&lt;br/&gt;            Description=&amp;#39;Installs all binaries and the license.&amp;#39;&lt;br/&gt;            Level=&amp;#39;1&amp;#39;&lt;br/&gt;            ConfigurableDirectory=&amp;#39;APPLICATIONFOLDER&amp;#39;&lt;br/&gt;            AllowAdvertise=&amp;#39;no&amp;#39;&lt;br/&gt;            Display=&amp;#39;expand&amp;#39;&lt;br/&gt;            Absent=&amp;#39;disallow&amp;#39;&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Uncomment the following `ComponentRef` tag to add the license&lt;br/&gt;              sidecar file to the installer.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;!--&amp;lt;ComponentRef Id=&amp;#39;License&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary0&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;Feature&lt;br/&gt;                Id=&amp;#39;Environment&amp;#39;&lt;br/&gt;                Title=&amp;#39;PATH Environment Variable&amp;#39;&lt;br/&gt;                Description=&amp;#39;Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.&amp;#39;&lt;br/&gt;                Level=&amp;#39;1&amp;#39;&lt;br/&gt;                Absent=&amp;#39;allow&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;ComponentRef Id=&amp;#39;Path&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;/Feature&amp;gt;&lt;br/&gt;        &amp;lt;/Feature&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;SetProperty Id=&amp;#39;ARPINSTALLLOCATION&amp;#39; Value=&amp;#39;[APPLICATIONFOLDER]&amp;#39; After=&amp;#39;CostFinalize&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the following `Icon` and `Property` tags to change the product icon.&lt;br/&gt;&lt;br/&gt;          The product icon is the graphic that appears in the Add/Remove&lt;br/&gt;          Programs control panel for the application.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Icon Id=&amp;#39;ProductICO&amp;#39; SourceFile=&amp;#39;wix\Product.ico&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Property Id=&amp;#39;ARPPRODUCTICON&amp;#39; Value=&amp;#39;ProductICO&amp;#39; /&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;ARPHELPLINK&amp;#39; Value=&amp;#39;&lt;a href=&#34;https://n34.dev/commands.html&amp;#39;/&amp;gt&#34;&gt;https://n34.dev/commands.html&amp;#39;/&amp;gt&lt;/a&gt;;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;UI&amp;gt;&lt;br/&gt;            &amp;lt;UIRef Id=&amp;#39;WixUI_FeatureTree&amp;#39;/&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Enabling the EULA dialog in the installer is a three step process:&lt;br/&gt;&lt;br/&gt;                1. Comment out or remove the two `Publish` tags that follow the&lt;br/&gt;                   `WixVariable` tag.&lt;br/&gt;                2. Uncomment the `&amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Path\to\Eula.rft&amp;#39;&amp;gt;` tag further down&lt;br/&gt;                3. Replace the `Value` attribute of the `WixVariable` tag with&lt;br/&gt;                   the path to a RTF file that will be used as the EULA and&lt;br/&gt;                   displayed in the license agreement dialog.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;WelcomeDlg&amp;#39; Control=&amp;#39;Next&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;CustomizeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;CustomizeDlg&amp;#39; Control=&amp;#39;Back&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;WelcomeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;/UI&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Enabling the EULA dialog in the installer requires uncommenting&lt;br/&gt;          the following `WixUILicenseRTF` tag and changing the `Value`&lt;br/&gt;          attribute.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!-- &amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Relative\Path\to\Eula.rtf&amp;#39;/&amp;gt; --&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom banner image across&lt;br/&gt;          the top of each screen. See the WiX Toolset documentation for details&lt;br/&gt;          about customization.&lt;br/&gt;&lt;br/&gt;          The banner BMP dimensions are 493 x 58 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIBannerBmp&amp;#39; Value=&amp;#39;wix\Banner.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom image to the first&lt;br/&gt;          dialog, or screen. See the WiX Toolset documentation for details about&lt;br/&gt;          customization.&lt;br/&gt;&lt;br/&gt;          The dialog BMP dimensions are 493 x 312 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIDialogBmp&amp;#39; Value=&amp;#39;wix\Dialog.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;/Product&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;/Wix&amp;gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:56:19&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs0gcya3mdkmvqhpmqx595qh2k4aekh7a0ejf8mf4k4m7wn6ulm9vqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrssqx3g9</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs0gcya3mdkmvqhpmqx595qh2k4aekh7a0ejf8mf4k4m7wn6ulm9vqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrssqx3g9" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    fmt,&lt;br/&gt;    fs,&lt;br/&gt;    path::{Path, PathBuf},&lt;br/&gt;    str::FromStr,&lt;br/&gt;    sync::atomic::Ordering,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventId, Kind, Tag, TagKind, TagStandard},&lt;br/&gt;    filter::Alphabet,&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::Coordinate,&lt;br/&gt;        nip10::Marker,&lt;br/&gt;        nip19::{Nip19Coordinate, Nip19Event, ToBech32},&lt;br/&gt;        nip34::GitRepositoryAnnouncement,&lt;br/&gt;        nip65::{self, RelayMetadata},&lt;br/&gt;    },&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use super::traits::TagsExt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{NOSTR_ADDRESS_FILE, parsers},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Returns the value of the given tag&lt;br/&gt;#[inline]&lt;br/&gt;fn tag_value(tag: &amp;amp;TagStandard) -&amp;gt; String {&lt;br/&gt;    tag.clone().to_vec().remove(1)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Parses the tag value into type `T` if possible.&lt;br/&gt;#[inline]&lt;br/&gt;fn parse_value&amp;lt;T: FromStr&amp;gt;(tag: &amp;amp;TagStandard) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;    tag_value(tag).parse().ok()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Gets all values from the tag. If any value fails to parse, returns an empty&lt;br/&gt;/// vector.&lt;br/&gt;#[inline]&lt;br/&gt;fn tag_values&amp;lt;T&amp;gt;(tag: &amp;amp;TagStandard) -&amp;gt; Vec&amp;lt;T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    T: FromStr &#43; fmt::Debug,&lt;br/&gt;    &amp;lt;T as FromStr&amp;gt;::Err: fmt::Debug,&lt;br/&gt;{&lt;br/&gt;    tag.clone()&lt;br/&gt;        .to_vec()&lt;br/&gt;        .into_iter()&lt;br/&gt;        .skip(1)&lt;br/&gt;        .map(|t| {&lt;br/&gt;            let result = T::from_str(t.as_str());&lt;br/&gt;            tracing::trace!(&amp;#34;Parsing `{t}` result: `{result:?}`&amp;#34;);&lt;br/&gt;            result&lt;br/&gt;        })&lt;br/&gt;        .collect::&amp;lt;Result&amp;lt;_, _&amp;gt;&amp;gt;()&lt;br/&gt;        .unwrap_or_default()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Convert [`Event`] to [`GitRepositoryAnnouncement`]&lt;br/&gt;pub fn event_into_repo(event: Event, repo_id: impl Into&amp;lt;String&amp;gt;) -&amp;gt; GitRepositoryAnnouncement {&lt;br/&gt;    let tags = &amp;amp;event.tags;&lt;br/&gt;&lt;br/&gt;    GitRepositoryAnnouncement {&lt;br/&gt;        id:          repo_id.into(),&lt;br/&gt;        name:        tags.map_tag(TagKind::Name, tag_value),&lt;br/&gt;        description: tags.map_tag(TagKind::Description, tag_value),&lt;br/&gt;        euc:         tags&lt;br/&gt;            .map_marker(&lt;br/&gt;                TagKind::single_letter(Alphabet::R, false),&lt;br/&gt;                &amp;#34;euc&amp;#34;,&lt;br/&gt;                parse_value,&lt;br/&gt;            )&lt;br/&gt;            .flatten(),&lt;br/&gt;        web:         tags.dmap_tag(TagKind::Web, tag_values),&lt;br/&gt;        clone:       tags.dmap_tag(TagKind::Clone, tag_values),&lt;br/&gt;        relays:      tags.dmap_tag(TagKind::Relays, tag_values),&lt;br/&gt;        maintainers: tags.dmap_tag(TagKind::Maintainers, tag_values),&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns a new string with leading and trailing whitespace removed.&lt;br/&gt;pub fn str_trim(s: String) -&amp;gt; String {&lt;br/&gt;    s.trim().to_owned()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns a vector with duplicate elements removed.&lt;br/&gt;pub fn dedup&amp;lt;I, T&amp;gt;(iter: I) -&amp;gt; Vec&amp;lt;T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    T: std::cmp::Ord,&lt;br/&gt;    I: Iterator&amp;lt;Item = T&amp;gt;,&lt;br/&gt;{&lt;br/&gt;    let mut vector: Vec&amp;lt;T&amp;gt; = iter.collect();&lt;br/&gt;    vector.sort_unstable();&lt;br/&gt;    vector.dedup();&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Sorts items from the iterator using the given key function.&lt;br/&gt;/// The sorting is unstable, but faster than stable sorting.&lt;br/&gt;pub fn sort_by_key&amp;lt;I, T, K&amp;gt;(iterator: I, key: impl FnMut(&amp;amp;T) -&amp;gt; K) -&amp;gt; impl Iterator&amp;lt;Item = T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    I: IntoIterator&amp;lt;Item = T&amp;gt;,&lt;br/&gt;    K: Ord,&lt;br/&gt;{&lt;br/&gt;    let mut vector = Vec::&amp;lt;T&amp;gt;::from_iter(iterator);&lt;br/&gt;    vector.sort_unstable_by_key(key);&lt;br/&gt;    vector.into_iter()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Creates a new NIP-19 nevent string from an event ID and up to 3 unique relay&lt;br/&gt;/// URLs.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn new_nevent(event_id: EventId, relays: &amp;amp;[RelayUrl]) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    Nip19Event::new(event_id)&lt;br/&gt;        .relays(&lt;br/&gt;            dedup(relays.iter().cloned())&lt;br/&gt;                .into_iter()&lt;br/&gt;                .take(3)&lt;br/&gt;                .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;(),&lt;br/&gt;        )&lt;br/&gt;        .to_bech32()&lt;br/&gt;        .map_err(N34Error::from)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Creates a NIP-19 naddr string for a git repository announcement and up to 3&lt;br/&gt;/// unique relay URLs.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn repo_naddr(&lt;br/&gt;    repo_id: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;    pubk: PublicKey,&lt;br/&gt;    relays: &amp;amp;[RelayUrl],&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    Nip19Coordinate::new(&lt;br/&gt;        Coordinate::new(Kind::GitRepoAnnouncement, pubk).identifier(repo_id),&lt;br/&gt;        dedup(relays.iter().cloned()).into_iter().take(3),&lt;br/&gt;    )&lt;br/&gt;    .to_bech32()&lt;br/&gt;    .map_err(N34Error::from)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts write relay URLs from an event if present, otherwise returns an&lt;br/&gt;/// empty vector.&lt;br/&gt;pub fn add_write_relays(event: Option&amp;lt;&amp;amp;Event&amp;gt;) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;    let mut vector = Vec::new();&lt;br/&gt;    if let Some(event) = event {&lt;br/&gt;        vector.extend(&lt;br/&gt;            nip65::extract_owned_relay_list(event.clone())&lt;br/&gt;                .filter_map(|(r, m)| m.is_none_or(|m| m == RelayMetadata::Write).then_some(r)),&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts read relay URLs from an event if present, otherwise returns an&lt;br/&gt;/// empty vector.&lt;br/&gt;pub fn add_read_relays(event: Option&amp;lt;&amp;amp;Event&amp;gt;) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;    let mut vector = Vec::new();&lt;br/&gt;    if let Some(event) = event {&lt;br/&gt;        vector.extend(&lt;br/&gt;            nip65::extract_owned_relay_list(event.clone())&lt;br/&gt;                .filter_map(|(r, m)| m.is_none_or(|m| m == RelayMetadata::Read).then_some(r)),&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Opens the user&amp;#39;s default editor ($EDITOR) to edit a temporary file with&lt;br/&gt;/// given suffix, then reads and returns the file contents. The temporary file&lt;br/&gt;/// is automatically deleted.&lt;br/&gt;pub fn read_editor(file_pre_content: Option&amp;lt;&amp;amp;str&amp;gt;, file_suffix: &amp;amp;str) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    let Ok(editor) = std::env::var(&amp;#34;EDITOR&amp;#34;) else {&lt;br/&gt;        return Err(N34Error::EditorNotFound);&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    let temp_path = tempfile::NamedTempFile::with_suffix(file_suffix)?.into_temp_path();&lt;br/&gt;&lt;br/&gt;    if let Some(pre_content) = file_pre_content {&lt;br/&gt;        fs::write(&amp;amp;temp_path, pre_content)?;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // Disable the logs to not show up in a terminal text editor&lt;br/&gt;    crate::EDITOR_OPEN.store(true, Ordering::Relaxed);&lt;br/&gt;    let exit_status = std::process::Command::new(&amp;amp;editor)&lt;br/&gt;        .arg(temp_path.to_str().expect(&amp;#34;The path is valid utf8&amp;#34;))&lt;br/&gt;        .spawn()?&lt;br/&gt;        .wait()?;&lt;br/&gt;    crate::EDITOR_OPEN.store(false, Ordering::Relaxed);&lt;br/&gt;&lt;br/&gt;    if !exit_status.success()&lt;br/&gt;        &amp;amp;&amp;amp; let Some(code) = exit_status.code()&lt;br/&gt;    {&lt;br/&gt;        return Err(N34Error::EditorErr(editor, code));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let content = fs::read_to_string(&amp;amp;temp_path)&lt;br/&gt;        .map_err(N34Error::from)?&lt;br/&gt;        .trim()&lt;br/&gt;        .to_owned();&lt;br/&gt;&lt;br/&gt;    if content.is_empty() {&lt;br/&gt;        return Err(N34Error::EmptyEditorFile);&lt;br/&gt;    }&lt;br/&gt;    Ok(content)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns the given content if it&amp;#39;s `Option::Some` or call [`read_editor`]&lt;br/&gt;pub fn get_content(&lt;br/&gt;    content: Option&amp;lt;impl AsRef&amp;lt;str&amp;gt;&amp;gt;,&lt;br/&gt;    quoted_content: Option&amp;lt;impl AsRef&amp;lt;str&amp;gt;&amp;gt;,&lt;br/&gt;    file_suffix: &amp;amp;str,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    if let Some(content) = content {&lt;br/&gt;        return Ok(content.as_ref().trim().to_owned());&lt;br/&gt;    }&lt;br/&gt;    read_editor(&lt;br/&gt;        quoted_content.map(|s| s.as_ref().to_owned()).as_deref(),&lt;br/&gt;        file_suffix,&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Path to the `nostr-address` file in current directory.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn nostr_address_path() -&amp;gt; std::io::Result&amp;lt;PathBuf&amp;gt; {&lt;br/&gt;    std::env::current_dir().map(|p| p.join(NOSTR_ADDRESS_FILE))&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns the given coordinates if Some, otherwise attempts to read and parse&lt;br/&gt;/// coordinates from the specified file. Returns an empty vector if the file&lt;br/&gt;/// doesn&amp;#39;t exist.&lt;br/&gt;pub fn naddrs_or_file(&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt;,&lt;br/&gt;    address_file_path: &amp;amp;Path,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    if let Some(naddrs) = naddrs {&lt;br/&gt;        return Ok(naddrs);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    if address_file_path.exists() {&lt;br/&gt;        parsers::parse_nostr_address_file(address_file_path)&lt;br/&gt;    } else {&lt;br/&gt;        Ok(Vec::new())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Generate a reply tag for an event with the given ID, relay URL (if any), and&lt;br/&gt;/// marker.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn event_reply_tag(reply_to: &amp;amp;EventId, relay: Option&amp;lt;&amp;amp;RelayUrl&amp;gt;, marker: Marker) -&amp;gt; Tag {&lt;br/&gt;    Tag::custom(&lt;br/&gt;        TagKind::e(),&lt;br/&gt;        [&lt;br/&gt;            reply_to.to_hex(),&lt;br/&gt;            relay.map(|r| r.to_string()).unwrap_or_default(),&lt;br/&gt;            marker.to_string(),&lt;br/&gt;        ],&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Wraps text into lines no longer than max_width, breaking only at whitespace.&lt;br/&gt;pub fn smart_wrap(text: &amp;amp;str, max_width: usize) -&amp;gt; String {&lt;br/&gt;    text.lines()&lt;br/&gt;        .map(|line| {&lt;br/&gt;            if !line.trim().is_empty() {&lt;br/&gt;                line.split(&amp;#34; &amp;#34;)&lt;br/&gt;                    .fold((String::new(), 0), |(result, last_newline), word| {&lt;br/&gt;                        let result_len = result.chars().count();&lt;br/&gt;                        if result_len == 0 {&lt;br/&gt;                            (word.to_owned(), 0)&lt;br/&gt;                        } else if (result_len - last_newline) &#43; word.chars().count() &amp;gt; max_width {&lt;br/&gt;                            (format!(&amp;#34;{result}\n{word}&amp;#34;), result_len &#43; 1)&lt;br/&gt;                        } else {&lt;br/&gt;                            (format!(&amp;#34;{result} {word}&amp;#34;), last_newline)&lt;br/&gt;                        }&lt;br/&gt;                    })&lt;br/&gt;                    .0&lt;br/&gt;            } else {&lt;br/&gt;                String::new()&lt;br/&gt;            }&lt;br/&gt;        })&lt;br/&gt;        .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;        .join(&amp;#34;\n&amp;#34;)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns an error if the given naddrs is empty otherwise returned it&lt;br/&gt;pub fn check_empty_naddrs(naddrs: Vec&amp;lt;Nip19Coordinate&amp;gt;) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    if naddrs.is_empty() {&lt;br/&gt;        return Err(N34Error::EmptyNaddrs);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    Ok(naddrs)&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:56:07&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqp8vxlcupwg8wuak4zn865nvlq9cuklmvxq8nqrtxznkj85j0yagzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsg9fk3f</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqp8vxlcupwg8wuak4zn865nvlq9cuklmvxq8nqrtxznkj85j0yagzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsg9fk3f" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use convert_case::{Case, Casing};&lt;br/&gt;use nostr::hashes::sha1::Hash as Sha1Hash;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventBuilder, EventId, Kind, Tag, TagKind, TagStandard, Tags},&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::Coordinate,&lt;br/&gt;        nip19::Nip19Coordinate,&lt;br/&gt;        nip21::Nip21,&lt;br/&gt;        nip34::{GitIssue, GitRepositoryAnnouncement},&lt;br/&gt;    },&lt;br/&gt;    parser::Token,&lt;br/&gt;    types::{RelayUrl, Url},&lt;br/&gt;};&lt;br/&gt;use nostr_keyring::KeyringError;&lt;br/&gt;&lt;br/&gt;use crate::cli::issue::ISSUE_ALT_PREFIX;&lt;br/&gt;use crate::cli::patch::{&lt;br/&gt;    LEGACY_NGIT_REVISION_ROOT_HASHTAG_CONTENT,&lt;br/&gt;    REVISION_ROOT_HASHTAG_CONTENT,&lt;br/&gt;    ROOT_HASHTAG_CONTENT,&lt;br/&gt;};&lt;br/&gt;use crate::error::{N34Error, N34Result};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// A trait to add helper instance function to [`Tags`] type&lt;br/&gt;#[easy_ext::ext(TagsExt)]&lt;br/&gt;impl Tags {&lt;br/&gt;    /// Search for the given tag and map it value to a function&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn map_tag&amp;lt;T&amp;gt;(&amp;amp;self, kind: TagKind, f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;        self.find_standardized(kind).map(f)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Search for the given tag and map it value to a function. If the tag not&lt;br/&gt;    /// found return the default `T`&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn dmap_tag&amp;lt;T&amp;gt;(&amp;amp;self, kind: TagKind, f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T) -&amp;gt; T&lt;br/&gt;    where&lt;br/&gt;        T: Default,&lt;br/&gt;    {&lt;br/&gt;        self.map_tag(kind, f).unwrap_or_default()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds the first standard tag of the given kind with the specified&lt;br/&gt;    /// marker, then applies the function to the tag and returns the result.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn map_marker&amp;lt;T&amp;gt;(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        kind: TagKind,&lt;br/&gt;        marker: &amp;amp;str,&lt;br/&gt;        f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T,&lt;br/&gt;    ) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;        self.filter_standardized(kind)&lt;br/&gt;            .find(|t| (*t).clone().to_vec().last().is_some_and(|m| m == marker))&lt;br/&gt;            .map(f)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Trait for building [`GitRepositoryAnnouncement`] events&lt;br/&gt;#[easy_ext::ext(NewGitRepositoryAnnouncement)]&lt;br/&gt;impl EventBuilder {&lt;br/&gt;    /// Creates a new [`GitRepositoryAnnouncement`] event builder with the given&lt;br/&gt;    /// repository details.&lt;br/&gt;    #[allow(clippy::too_many_arguments)]&lt;br/&gt;    pub fn new_git_repo(&lt;br/&gt;        repo_id: String,&lt;br/&gt;        name: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        description: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        web: Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;        clone: Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;        relays: Vec&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;        maintainers: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;        labels: Vec&amp;lt;String&amp;gt;,&lt;br/&gt;        force_id: bool,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;EventBuilder&amp;gt; {&lt;br/&gt;        let repo_id = repo_id.trim();&lt;br/&gt;        let kebab_repo_id = repo_id.to_case(Case::Kebab);&lt;br/&gt;        if repo_id.is_empty() || (!force_id &amp;amp;&amp;amp; repo_id != kebab_repo_id) {&lt;br/&gt;            if repo_id != kebab_repo_id {&lt;br/&gt;                tracing::error!(&lt;br/&gt;                    &amp;#34;The repo id should be `{kebab_repo_id}` (kebab-case). Use `--force-id` to \&lt;br/&gt;                     override this check&amp;#34;&lt;br/&gt;                );&lt;br/&gt;            }&lt;br/&gt;            return Err(N34Error::InvalidRepoId);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(&lt;br/&gt;            EventBuilder::git_repository_announcement(GitRepositoryAnnouncement {&lt;br/&gt;                id: repo_id.to_owned(),&lt;br/&gt;                name,&lt;br/&gt;                description,&lt;br/&gt;                web,&lt;br/&gt;                clone,&lt;br/&gt;                relays,&lt;br/&gt;                euc: None,&lt;br/&gt;                maintainers,&lt;br/&gt;            })?&lt;br/&gt;            .dedup_tags()&lt;br/&gt;            .tags(labels.into_iter().map(Tag::hashtag)),&lt;br/&gt;        )&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Creates a new [`GitIssue`] event builder with the given&lt;br/&gt;    /// issue details.&lt;br/&gt;    pub fn new_git_issue(&lt;br/&gt;        coordinates: &amp;amp;[Coordinate],&lt;br/&gt;        content: String,&lt;br/&gt;        subject: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        labels: Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;EventBuilder&amp;gt; {&lt;br/&gt;        let mut coordinates = coordinates.iter();&lt;br/&gt;        let first_coordinate = coordinates.next().ok_or(N34Error::EmptyNaddrs)?;&lt;br/&gt;&lt;br/&gt;        let mut event_builder = EventBuilder::git_issue(GitIssue {&lt;br/&gt;            repository: first_coordinate.clone(),&lt;br/&gt;            content,&lt;br/&gt;            subject: subject.clone(),&lt;br/&gt;            labels: labels.into_iter().map(|l| l.trim().to_owned()).collect(),&lt;br/&gt;        })&lt;br/&gt;        .map_err(N34Error::from)?&lt;br/&gt;        .tags(&lt;br/&gt;            coordinates&lt;br/&gt;                .clone()&lt;br/&gt;                .map(|c| Tag::coordinate(c.clone(), None)),&lt;br/&gt;        )&lt;br/&gt;        .tags(coordinates.map(|c| Tag::public_key(c.public_key)));&lt;br/&gt;&lt;br/&gt;        if let Some(issue_subject) = subject {&lt;br/&gt;            event_builder =&lt;br/&gt;                event_builder.tag(Tag::alt(format!(&amp;#34;{ISSUE_ALT_PREFIX}{issue_subject}&amp;#34;)))&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(event_builder)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Helper functions for [`Token`] type&lt;br/&gt;#[easy_ext::ext(TokenUtils)]&lt;br/&gt;impl Token&amp;lt;&amp;#39;_&amp;gt; {&lt;br/&gt;    /// Returns `Some((public_key, relays))` from the givin token if it&amp;#39;s npub1&lt;br/&gt;    /// or nprofile1&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_public_key(&amp;amp;self) -&amp;gt; Option&amp;lt;(PublicKey, Vec&amp;lt;RelayUrl&amp;gt;)&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Nostr(nip21) =&amp;gt; {&lt;br/&gt;                match nip21 {&lt;br/&gt;                    Nip21::Pubkey(pkey) =&amp;gt; Some((*pkey, Vec::new())),&lt;br/&gt;                    Nip21::Profile(profile) =&amp;gt; Some((profile.public_key, profile.relays.clone())),&lt;br/&gt;                    _ =&amp;gt; None,&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns `Some((note_id, relays))` from the givin token if it&amp;#39;s note1 or&lt;br/&gt;    /// nevent1&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_event_id(&amp;amp;self) -&amp;gt; Option&amp;lt;(EventId, Vec&amp;lt;RelayUrl&amp;gt;)&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Nostr(nip21) =&amp;gt; {&lt;br/&gt;                match nip21 {&lt;br/&gt;                    Nip21::EventId(event_id) =&amp;gt; Some((*event_id, Vec::new())),&lt;br/&gt;                    Nip21::Event(event) =&amp;gt; Some((event.event_id, event.relays.clone())),&lt;br/&gt;                    _ =&amp;gt; None,&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns `Some(hashtag)` from the givin token if it&amp;#39;s a hashtag&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_hashtag(&amp;amp;self) -&amp;gt; Option&amp;lt;String&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Hashtag(tag) =&amp;gt; Some(tag.trim().to_owned()),&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with lists of NIP-19 coordinates&lt;br/&gt;#[easy_ext::ext(NaddrsUtils)]&lt;br/&gt;impl Vec&amp;lt;Nip19Coordinate&amp;gt; {&lt;br/&gt;    /// Converts these coordinate addresses to basic coordinates&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn into_coordinates(self) -&amp;gt; Vec&amp;lt;Coordinate&amp;gt; {&lt;br/&gt;        self.into_iter().map(|n| n.coordinate).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns all repository owners&amp;#39; public keys from these coordinates.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_owners(&amp;amp;self) -&amp;gt; Vec&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.iter().map(|n| n.public_key).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Extracts all relay URLs from these coordinates&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_relays(&amp;amp;self) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|n| n.relays.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with lists of repository announcement&lt;br/&gt;#[easy_ext::ext(ReposUtils)]&lt;br/&gt;impl Vec&amp;lt;GitRepositoryAnnouncement&amp;gt; {&lt;br/&gt;    /// Extracts all relay URLs from these repositories&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_relays(&amp;amp;self) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|n| n.relays.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Extract all the maintainers from these repositories&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_maintainers(&amp;amp;self) -&amp;gt; Vec&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|r| r.maintainers.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the first EUC hash from the reposotoies if it exists.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_euc(&amp;amp;self) -&amp;gt; Option&amp;lt;&amp;amp;Sha1Hash&amp;gt; {&lt;br/&gt;        self.iter().find_map(|r| r.euc.as_ref())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with patch events&lt;br/&gt;#[easy_ext::ext(GitPatchUtils)]&lt;br/&gt;impl Event {&lt;br/&gt;    /// Returns whether the patch is a root or not&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_root_patch(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        self.kind == Kind::GitPatch&lt;br/&gt;            &amp;amp;&amp;amp; self&lt;br/&gt;                .tags&lt;br/&gt;                .filter(TagKind::t())&lt;br/&gt;                .any(|t| t.content() == Some(ROOT_HASHTAG_CONTENT))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns whether the patch is patch-revision or not&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_revision_patch(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        self.kind == Kind::GitPatch&lt;br/&gt;            &amp;amp;&amp;amp; self.tags.filter(TagKind::t()).any(|t| {&lt;br/&gt;                [&lt;br/&gt;                    Some(REVISION_ROOT_HASHTAG_CONTENT),&lt;br/&gt;                    Some(LEGACY_NGIT_REVISION_ROOT_HASHTAG_CONTENT),&lt;br/&gt;                ]&lt;br/&gt;                .contains(&amp;amp;t.content())&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the root patch ID from a patch-revision event by finding the `e`&lt;br/&gt;    /// tag that replies to it. Fails if no such tag is found or if the tag&lt;br/&gt;    /// contains an invalid event ID.&lt;br/&gt;    pub fn root_patch_from_revision(&amp;amp;self) -&amp;gt; N34Result&amp;lt;EventId&amp;gt; {&lt;br/&gt;        self.tags&lt;br/&gt;            .iter()&lt;br/&gt;            .find(|tag| tag.is_reply())&lt;br/&gt;            .ok_or_else(|| {&lt;br/&gt;                N34Error::InvalidEvent(&lt;br/&gt;                    &amp;#34;A patch revision without `e`-reply to the root patch&amp;#34;.to_owned(),&lt;br/&gt;                )&lt;br/&gt;            })?&lt;br/&gt;            .content()&lt;br/&gt;            .ok_or_else(|| N34Error::InvalidEvent(&amp;#34;`e` tag without an event&amp;#34;.to_owned()))?&lt;br/&gt;            .parse()&lt;br/&gt;            .map_err(|err| N34Error::InvalidEvent(format!(&amp;#34;Invalid event ID in `e` tag: {err}&amp;#34;)))&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with issue events&lt;br/&gt;#[easy_ext::ext(GitIssueUtils)]&lt;br/&gt;impl Event {&lt;br/&gt;    /// Gets the subject line of the issue or &amp;#34;N/A&amp;#34; if none exists&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_issue_subject(&amp;amp;self) -&amp;gt; &amp;amp;str {&lt;br/&gt;        self.tags&lt;br/&gt;            .find(TagKind::Subject)&lt;br/&gt;            .and_then(|t| t.content())&lt;br/&gt;            .unwrap_or(&amp;#34;N/A&amp;#34;)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets all issue labels formatted as comma-separated hashtags (e.g. &amp;#34;#bug,&lt;br/&gt;    /// #feature&amp;#34;)&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_issue_labels(&amp;amp;self) -&amp;gt; String {&lt;br/&gt;        self.tags&lt;br/&gt;            .filter(TagKind::t())&lt;br/&gt;            .filter_map(|t| t.content().map(|l| format!(&amp;#34;#{l}&amp;#34;)))&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;            .join(&amp;#34;, &amp;#34;)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(NostrKeyringErrorUtils)]&lt;br/&gt;impl nostr_keyring::Error {&lt;br/&gt;    /// Checks if the error indicates a missing keyring entry.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_keyring_no_entry(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, nostr_keyring::Error::Keyring(KeyringError::NoEntry))&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:55:53&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsw9zjk3l82m9x3wvf4a74xvp9984gezwtxqf97l5rc575sqq4nyyszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs558vfw</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsw9zjk3l82m9x3wvf4a74xvp9984gezwtxqf97l5rc575sqq4nyyszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs558vfw" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Extension traits for nostr types.&lt;br/&gt;pub mod traits;&lt;br/&gt;/// Utility functions for nostr.&lt;br/&gt;pub mod utils;&lt;br/&gt;&lt;br/&gt;use std::{collections::HashSet, time::Duration};&lt;br/&gt;&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventId, Kind, Tag, TagKind, TagStandard, Tags, UnsignedEvent},&lt;br/&gt;    filter::Filter,&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::{Coordinate, Metadata},&lt;br/&gt;        nip19::ToBech32,&lt;br/&gt;        nip22,&lt;br/&gt;        nip34::GitRepositoryAnnouncement,&lt;br/&gt;    },&lt;br/&gt;    parser::NostrParser,&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;use nostr_sdk::{Client, ClientOptions};&lt;br/&gt;use traits::TokenUtils;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, issue::IssueStatus, patch::PatchStatus},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Timeout duration for the client.&lt;br/&gt;const CLIENT_TIMEOUT: Duration = Duration::from_millis(1500);&lt;br/&gt;/// Length of a Nostr npub (public key) in characters.&lt;br/&gt;const NPUB_LEN: usize = 63;&lt;br/&gt;&lt;br/&gt;/// Parsed content details&lt;br/&gt;#[derive(Clone)]&lt;br/&gt;pub struct ContentDetails {&lt;br/&gt;    /// Public keys of users mentioned in the content.&lt;br/&gt;    pub p_tagged:     HashSet&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    /// Event IDs and optional relay URLs for quoted events.&lt;br/&gt;    pub quotes:       HashSet&amp;lt;(EventId, Option&amp;lt;RelayUrl&amp;gt;)&amp;gt;,&lt;br/&gt;    /// Hashtags found in the content.&lt;br/&gt;    pub hashtags:     HashSet&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Relays where mentioned users and quoted authors are read.&lt;br/&gt;    pub write_relays: HashSet&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// A client for interacting with the Nostr relays&lt;br/&gt;#[derive(Clone)]&lt;br/&gt;pub struct NostrClient {&lt;br/&gt;    /// The underlying Nostr client implementation&lt;br/&gt;    pub client: Client,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl ContentDetails {&lt;br/&gt;    /// Create a new [`ContentDetails`] instance&lt;br/&gt;    pub fn new(&lt;br/&gt;        users: impl IntoIterator&amp;lt;Item = PublicKey&amp;gt;,&lt;br/&gt;        quotes: impl IntoIterator&amp;lt;Item = (EventId, Option&amp;lt;RelayUrl&amp;gt;)&amp;gt;,&lt;br/&gt;        hashtags: impl IntoIterator&amp;lt;Item = String&amp;gt;,&lt;br/&gt;        write_relays: impl IntoIterator&amp;lt;Item = RelayUrl&amp;gt;,&lt;br/&gt;    ) -&amp;gt; Self {&lt;br/&gt;        Self {&lt;br/&gt;            p_tagged:     HashSet::from_iter(users),&lt;br/&gt;            quotes:       HashSet::from_iter(quotes),&lt;br/&gt;            hashtags:     HashSet::from_iter(hashtags),&lt;br/&gt;            write_relays: HashSet::from_iter(write_relays),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Converts the instance into a list of tags including hashtags, p-tagged&lt;br/&gt;    /// users, and quoted events.&lt;br/&gt;    pub fn into_tags(self) -&amp;gt; Tags {&lt;br/&gt;        let mut tags = Tags::new();&lt;br/&gt;        tags.extend(self.hashtags.into_iter().map(Tag::hashtag));&lt;br/&gt;        tags.extend(self.p_tagged.into_iter().map(Tag::public_key));&lt;br/&gt;        tags.extend(self.quotes.into_iter().map(|(event_id, relay_url)| {&lt;br/&gt;            // TODO: Add the author public key if we know it&lt;br/&gt;            Tag::from_standardized(TagStandard::Quote {&lt;br/&gt;                event_id,&lt;br/&gt;                relay_url,&lt;br/&gt;                public_key: None,&lt;br/&gt;            })&lt;br/&gt;        }));&lt;br/&gt;        tags&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl NostrClient {&lt;br/&gt;    /// Creates a new [`NostrClient`] with the given client and options.&lt;br/&gt;    const fn new(client: Client) -&amp;gt; Self {&lt;br/&gt;        Self { client }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Initializes a new [`NostrClient`] instance and connects to the specified&lt;br/&gt;    /// relays.&lt;br/&gt;    pub async fn init(options: &amp;amp;CliOptions, relays: &amp;amp;[RelayUrl]) -&amp;gt; Self {&lt;br/&gt;        let mut client_builder =&lt;br/&gt;            Client::builder().opts(ClientOptions::new().verify_subscriptions(true));&lt;br/&gt;&lt;br/&gt;        if let Ok(Some(signer)) = options.signer().await {&lt;br/&gt;            client_builder = client_builder.signer(signer);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let client = Self::new(client_builder.build());&lt;br/&gt;&lt;br/&gt;        client.add_relays(relays).await;&lt;br/&gt;        client&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    //// Returns the users public key&lt;br/&gt;    pub async fn pubkey(&amp;amp;self) -&amp;gt; N34Result&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.client&lt;br/&gt;            .signer()&lt;br/&gt;            .await?&lt;br/&gt;            .get_public_key()&lt;br/&gt;            .await&lt;br/&gt;            .map_err(N34Error::SignerError)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Add relays and connect to them&lt;br/&gt;    pub async fn add_relays(&amp;amp;self, relays: &amp;amp;[RelayUrl]) {&lt;br/&gt;        if relays.is_empty() {&lt;br/&gt;            return;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let mut tasks = Vec::new();&lt;br/&gt;        for relay in relays {&lt;br/&gt;            let relay = relay.clone();&lt;br/&gt;            let client = self.client.clone();&lt;br/&gt;            tasks.push(tokio::spawn(async move {&lt;br/&gt;                client&lt;br/&gt;                    .add_relay(&amp;amp;relay)&lt;br/&gt;                    .await&lt;br/&gt;                    .expect(&amp;#34;It&amp;#39;s a valid relay url&amp;#34;);&lt;br/&gt;                if let Err(err) = client.try_connect_relay(&amp;amp;relay, CLIENT_TIMEOUT).await {&lt;br/&gt;                    tracing::error!(&amp;#34;Failed to connect to relay &amp;#39;{relay}&amp;#39;: {err}&amp;#34;);&lt;br/&gt;                }&lt;br/&gt;            }));&lt;br/&gt;        }&lt;br/&gt;        future::join_all(tasks).await;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Add a relay hint and connect to it&lt;br/&gt;    pub async fn add_relay_hint(&amp;amp;self, hint: Option&amp;lt;RelayUrl&amp;gt;) {&lt;br/&gt;        if let Some(relay) = hint {&lt;br/&gt;            self.add_relays(&amp;amp;[relay]).await&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// broadcast an event to the given relays&lt;br/&gt;    pub async fn broadcast(&amp;amp;self, event: &amp;amp;Event, relays: &amp;amp;[RelayUrl]) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        self.client.send_event_to(relays, event).await?;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Broadcasts an unsigned event to given relays, optionally broadcast the&lt;br/&gt;    /// relays list event. Returns URLs of relays that successfully received&lt;br/&gt;    /// the event.&lt;br/&gt;    pub async fn send_event_to(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        mut event: UnsignedEvent,&lt;br/&gt;        relays_list: Option&amp;lt;&amp;amp;Event&amp;gt;,&lt;br/&gt;        relays: &amp;amp;[RelayUrl],&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;RelayUrl&amp;gt;&amp;gt; {&lt;br/&gt;        self.add_relays(relays).await;&lt;br/&gt;        let event_id = event.id();&lt;br/&gt;&lt;br/&gt;        let (result, ..) = futures::join!(&lt;br/&gt;            async {&lt;br/&gt;                N34Result::Ok(&lt;br/&gt;                    self.client&lt;br/&gt;                        .send_event_to(relays, &amp;amp;event.sign(&amp;amp;self.client.signer().await?).await?)&lt;br/&gt;                        .await?,&lt;br/&gt;                )&lt;br/&gt;            },&lt;br/&gt;            async {&lt;br/&gt;                if let Some(event) = relays_list {&lt;br/&gt;                    let _ = self.client.send_event_to(relays, event).await;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        );&lt;br/&gt;        let result = result?;&lt;br/&gt;&lt;br/&gt;        for relay in &amp;amp;result.success {&lt;br/&gt;            tracing::info!(event_id = %event_id, relay = %relay, &amp;#34;Event sent successfully&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;        for (relay, reason) in &amp;amp;result.failed {&lt;br/&gt;            tracing::warn!(event_id = %event_id, relay = %relay, reason = %reason, &amp;#34;Failed to send event&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(result.success.into_iter().collect())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the first event matching the given filter, or None if no event&lt;br/&gt;    /// is found.&lt;br/&gt;    pub async fn fetch_event(&amp;amp;self, filter: Filter) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .client&lt;br/&gt;            .fetch_events(filter.limit(1), CLIENT_TIMEOUT)&lt;br/&gt;            .await?&lt;br/&gt;            .first_owned())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the events matching the given filter&lt;br/&gt;    pub async fn fetch_events(&amp;amp;self, filter: Filter) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        // Multiply timeout by 5 to account for multiple events being fetched&lt;br/&gt;        Ok(self&lt;br/&gt;            .client&lt;br/&gt;            .fetch_events(filter, CLIENT_TIMEOUT * 5)&lt;br/&gt;            .await?&lt;br/&gt;            .to_vec())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Try to fetch the repositories and returns them&lt;br/&gt;    pub async fn fetch_repos(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        repo_naddrs: &amp;amp;[Coordinate],&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;GitRepositoryAnnouncement&amp;gt;&amp;gt; {&lt;br/&gt;        future::join_all(repo_naddrs.iter().map(|c| {&lt;br/&gt;            async {&lt;br/&gt;                self.fetch_event(&lt;br/&gt;                    Filter::new()&lt;br/&gt;                        .author(c.public_key)&lt;br/&gt;                        .identifier(&amp;amp;c.identifier)&lt;br/&gt;                        .kind(Kind::GitRepoAnnouncement),&lt;br/&gt;                )&lt;br/&gt;                .await?&lt;br/&gt;                .map(|e| utils::event_into_repo(e, &amp;amp;c.identifier))&lt;br/&gt;                .ok_or(N34Error::NotFoundRepo)&lt;br/&gt;            }&lt;br/&gt;        }))&lt;br/&gt;        .await&lt;br/&gt;        .into_iter()&lt;br/&gt;        .collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetch the patch by the given id. None if not found&lt;br/&gt;    pub async fn fetch_patch(&amp;amp;self, patch_id: EventId) -&amp;gt; N34Result&amp;lt;Event&amp;gt; {&lt;br/&gt;        self.fetch_event(Filter::new().id(patch_id).kind(Kind::GitPatch))&lt;br/&gt;            .await?&lt;br/&gt;            .ok_or(N34Error::CanNotFoundPatch)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the username for a given public key. If no username is found,&lt;br/&gt;    /// falls back to a shortened version of the public key.&lt;br/&gt;    pub async fn get_username(&amp;amp;self, user: PublicKey) -&amp;gt; String {&lt;br/&gt;        self.fetch_event(Filter::new().kind(Kind::Metadata).author(user))&lt;br/&gt;            .await&lt;br/&gt;            .ok()&lt;br/&gt;            .flatten()&lt;br/&gt;            .and_then(|e| Metadata::try_from(&amp;amp;e).ok())&lt;br/&gt;            .and_then(|m| m.display_name.or(m.name))&lt;br/&gt;            .unwrap_or_else(|| {&lt;br/&gt;                let pubkey = user.to_bech32().expect(&amp;#34;The error is `Infallible`&amp;#34;);&lt;br/&gt;                format!(&amp;#34;{}...{}&amp;#34;, &amp;amp;pubkey[..8], &amp;amp;pubkey[NPUB_LEN - 8..])&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Get the latest status of an issue by its ID, only considering status&lt;br/&gt;    /// events from authorized_pubkeys. If no valid status event is found,&lt;br/&gt;    /// defaults to Open.&lt;br/&gt;    pub async fn fetch_issue_status(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        issue_id: EventId,&lt;br/&gt;        authorized_pubkeys: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;IssueStatus&amp;gt; {&lt;br/&gt;        self.fetch_events(&lt;br/&gt;            Filter::new()&lt;br/&gt;                .event(issue_id)&lt;br/&gt;                .kinds([&lt;br/&gt;                    Kind::GitStatusOpen,&lt;br/&gt;                    Kind::GitStatusApplied,&lt;br/&gt;                    Kind::GitStatusClosed,&lt;br/&gt;                ])&lt;br/&gt;                .authors(utils::dedup(authorized_pubkeys.into_iter())),&lt;br/&gt;        )&lt;br/&gt;        .await?&lt;br/&gt;        .into_iter()&lt;br/&gt;        .max_by_key(|e| e.created_at)&lt;br/&gt;        .map(|status| IssueStatus::try_from(status.kind))&lt;br/&gt;        .unwrap_or_else(|| Ok(IssueStatus::Open))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the status of a patch. If it&amp;#39;s a revision patch, checks if it&amp;#39;s&lt;br/&gt;    /// closed when the root patch is already merged/applied but doesn&amp;#39;t&lt;br/&gt;    /// reference this revision. Defaults to Open status if no status event&lt;br/&gt;    /// is found.&lt;br/&gt;    pub async fn fetch_patch_status(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        root_patch: EventId,&lt;br/&gt;        root_revision: Option&amp;lt;EventId&amp;gt;,&lt;br/&gt;        authorized_pubkeys: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;PatchStatus&amp;gt; {&lt;br/&gt;        let (root_status, event_tags) = self&lt;br/&gt;            .fetch_events(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .event(root_patch)&lt;br/&gt;                    .kinds([&lt;br/&gt;                        Kind::GitStatusOpen,&lt;br/&gt;                        Kind::GitStatusApplied,&lt;br/&gt;                        Kind::GitStatusClosed,&lt;br/&gt;                        Kind::GitStatusDraft,&lt;br/&gt;                    ])&lt;br/&gt;                    .authors(utils::dedup(authorized_pubkeys.into_iter())),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .into_iter()&lt;br/&gt;            .max_by_key(|e| e.created_at)&lt;br/&gt;            .map(|status| N34Result::Ok((PatchStatus::try_from(status.kind)?, status.tags)))&lt;br/&gt;            .unwrap_or_else(|| Ok((PatchStatus::Open, Tags::new())))?;&lt;br/&gt;&lt;br/&gt;        if let Some(revision_id) = root_revision&lt;br/&gt;            &amp;amp;&amp;amp; root_status.is_merged_or_applied()&lt;br/&gt;            &amp;amp;&amp;amp; !event_tags&lt;br/&gt;                .filter(TagKind::e())&lt;br/&gt;                .any(|t| t.is_reply() &amp;amp;&amp;amp; t.content().is_some_and(|c| c == revision_id.to_hex()))&lt;br/&gt;        {&lt;br/&gt;            return Ok(PatchStatus::Closed);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(root_status)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    pub async fn fetch_patch_series(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        root_patch_id: EventId,&lt;br/&gt;        root_patch_author: PublicKey,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .fetch_events(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .kind(Kind::GitPatch)&lt;br/&gt;                    .author(root_patch_author)&lt;br/&gt;                    .event(root_patch_id),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .into_iter()&lt;br/&gt;            .filter(|e| {&lt;br/&gt;                e.tags.iter().any(|t| {&lt;br/&gt;                    t.is_root() &amp;amp;&amp;amp; t.content().is_some_and(|c| c == root_patch_id.to_hex())&lt;br/&gt;                })&lt;br/&gt;            })&lt;br/&gt;            .collect())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds the root issue or patch for a given event. If the event is already&lt;br/&gt;    /// a root (issue/patch), returns it directly. For comments, follows&lt;br/&gt;    /// parent/root references until finding the root or failing. Returns&lt;br/&gt;    /// None if no root can be found.&lt;br/&gt;    pub async fn find_root(&amp;amp;self, mut event: Event) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        if !matches!(event.kind, Kind::GitIssue | Kind::GitPatch | Kind::Comment) {&lt;br/&gt;            return Err(N34Error::CanNotReplyToEvent);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        loop {&lt;br/&gt;            if matches!(event.kind, Kind::GitIssue | Kind::GitPatch) {&lt;br/&gt;                return Ok(Some(event));&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            if let Some(nip22::CommentTarget::Event { id, relay_hint, .. }) =&lt;br/&gt;                nip22::extract_root(&amp;amp;event)&lt;br/&gt;            {&lt;br/&gt;                self.add_relay_hint(relay_hint.cloned()).await;&lt;br/&gt;                let root_event = self.fetch_event(Filter::new().id(*id)).await?;&lt;br/&gt;                if let Some(ref root_event) = root_event&lt;br/&gt;                    &amp;amp;&amp;amp; !matches!(root_event.kind, Kind::GitIssue | Kind::GitPatch)&lt;br/&gt;                {&lt;br/&gt;                    return Err(N34Error::CanNotReplyToEvent);&lt;br/&gt;                }&lt;br/&gt;                return Ok(root_event);&lt;br/&gt;            } else if let Some(nip22::CommentTarget::Event { id, relay_hint, .. }) =&lt;br/&gt;                nip22::extract_parent(&amp;amp;event)&lt;br/&gt;            {&lt;br/&gt;                self.add_relay_hint(relay_hint.cloned()).await;&lt;br/&gt;                if let Ok(Some(parent_event)) = self.fetch_event(Filter::new().id(*id)).await {&lt;br/&gt;                    event = parent_event;&lt;br/&gt;                    continue;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            // Break if: no root/parent tags found, parent/root event fetch failed&lt;br/&gt;            break;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(None)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the relay list (kind 10002) for the given user. Returns None if&lt;br/&gt;    /// no relays are found.&lt;br/&gt;    pub async fn user_relays_list(&amp;amp;self, user: PublicKey) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        self.fetch_event(Filter::new().author(user).kind(Kind::RelayList))&lt;br/&gt;            .await&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the author of the specified event, if found.&lt;br/&gt;    pub async fn event_author(&amp;amp;self, event_id: EventId) -&amp;gt; N34Result&amp;lt;Option&amp;lt;PublicKey&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .fetch_event(Filter::new().id(event_id))&lt;br/&gt;            .await?&lt;br/&gt;            .map(|e| e.pubkey))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the read relays of the given user if found, otherwise empty&lt;br/&gt;    /// vector&lt;br/&gt;    pub async fn read_relays_from_user(&amp;amp;self, user: PublicKey) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        utils::add_read_relays(self.user_relays_list(user).await.ok().flatten().as_ref())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the read relays of the given users if found, otherwise empty&lt;br/&gt;    /// vector&lt;br/&gt;    pub async fn read_relays_from_users(&amp;amp;self, users: &amp;amp;[PublicKey]) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.fetch_events(&lt;br/&gt;            Filter::new()&lt;br/&gt;                .kind(nostr::event::Kind::RelayList)&lt;br/&gt;                .authors(utils::dedup(users.iter().copied())),&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;        .unwrap_or_default()&lt;br/&gt;        .into_iter()&lt;br/&gt;        .flat_map(|e| utils::add_read_relays(Some(&amp;amp;e)))&lt;br/&gt;        .collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Parse the given content and returns the details that inside it&lt;br/&gt;    pub async fn parse_content(&amp;amp;self, content: &amp;amp;str) -&amp;gt; ContentDetails {&lt;br/&gt;        let mut write_relays = Vec::new();&lt;br/&gt;        let tokens = NostrParser::new().parse(content).collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;        let mut p_tagged_users = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_public_key)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;        let quotes = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_event_id)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;        let hashtags = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_hashtag)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;        for (user, relays) in &amp;amp;p_tagged_users {&lt;br/&gt;            self.add_relays(relays).await;&lt;br/&gt;            write_relays.extend(self.read_relays_from_user(*user).await);&lt;br/&gt;        }&lt;br/&gt;        for (event_id, relays) in &amp;amp;quotes {&lt;br/&gt;            self.add_relays(relays).await;&lt;br/&gt;            // Add the event author to the p-tagged users&lt;br/&gt;            if let Ok(Some(author)) = self.event_author(*event_id).await {&lt;br/&gt;                p_tagged_users.push((author, Vec::new()));&lt;br/&gt;                write_relays.extend(self.read_relays_from_user(author).await);&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        ContentDetails::new(&lt;br/&gt;            p_tagged_users.into_iter().map(|(p, _)| p),&lt;br/&gt;            quotes.into_iter().map(|(e, r)| (e, r.first().cloned())),&lt;br/&gt;            hashtags,&lt;br/&gt;            write_relays,&lt;br/&gt;        )&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:55:39&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs9d7v4lf4mrqjf827lvy3j97ermjhedkfvuwx9cdmn33rvaqkkgyqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsl2pw7v</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs9d7v4lf4mrqjf827lvy3j97ermjhedkfvuwx9cdmn33rvaqkkgyqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsl2pw7v" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Command line interface module&lt;br/&gt;pub mod cli;&lt;br/&gt;/// N34 errors&lt;br/&gt;pub mod error;&lt;br/&gt;/// Nostr utils module&lt;br/&gt;pub mod nostr_utils;&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    process::ExitCode,&lt;br/&gt;    sync::atomic::{AtomicBool, Ordering},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Parser;&lt;br/&gt;use clap_verbosity_flag::Verbosity;&lt;br/&gt;use tracing::Level;&lt;br/&gt;use tracing_subscriber::{Layer, filter, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;use self::cli::Cli;&lt;br/&gt;&lt;br/&gt;/// Whether the editor is currently open. Prevents logging while the editor is&lt;br/&gt;/// open.&lt;br/&gt;static EDITOR_OPEN: AtomicBool = AtomicBool::new(false);&lt;br/&gt;&lt;br/&gt;/// Configures the logging level based on the provided verbosity.&lt;br/&gt;///&lt;br/&gt;/// When verbosity is set to TRACE, includes file and line numbers in logs.&lt;br/&gt;fn set_log_level(verbosity: Verbosity) {&lt;br/&gt;    let is_trace = verbosity&lt;br/&gt;        .tracing_level()&lt;br/&gt;        .is_some_and(|l| l == tracing::Level::TRACE);&lt;br/&gt;&lt;br/&gt;    let logs_filter = filter::dynamic_filter_fn(move |m, _| {&lt;br/&gt;        // Disable all logs while editor is open&lt;br/&gt;        verbosity.tracing_level().unwrap_or(Level::ERROR) &amp;gt;= *m.level()&lt;br/&gt;            &amp;amp;&amp;amp; !EDITOR_OPEN.load(Ordering::Relaxed)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    let logs_layer = tracing_subscriber::fmt::layer()&lt;br/&gt;        .with_file(is_trace)&lt;br/&gt;        .with_line_number(is_trace)&lt;br/&gt;        .without_time();&lt;br/&gt;    let subscriber = tracing_subscriber::registry().with(logs_layer.with_filter(logs_filter));&lt;br/&gt;    tracing::subscriber::set_global_default(subscriber).ok();&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() -&amp;gt; ExitCode {&lt;br/&gt;    let cli = match cli::post_cli(Cli::parse()) {&lt;br/&gt;        Ok(cli) =&amp;gt; cli,&lt;br/&gt;        Err(err) =&amp;gt; {&lt;br/&gt;            eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;            return ExitCode::FAILURE;&lt;br/&gt;        }&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    set_log_level(cli.verbosity);&lt;br/&gt;&lt;br/&gt;    if let Err(err) = cli.run().await {&lt;br/&gt;        tracing::error!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;        return err.exit_code();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    ExitCode::SUCCESS&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:55:25&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2uu7q3ttcer2mrcq9t0s4zdf3whf5n9appjvrh02rshx9qeukekszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsguaq0f</id>
    
      <title type="html">#![allow(unused)] // n34 - A CLI to interact with NIP-34 and ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2uu7q3ttcer2mrcq9t0s4zdf3whf5n9appjvrh02rshx9qeukekszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsguaq0f" />
    <content type="html">
      #![allow(unused)]&lt;br/&gt;// n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Command line interface module&lt;br/&gt;pub mod cli;&lt;br/&gt;/// N34 errors&lt;br/&gt;pub mod error;&lt;br/&gt;/// Nostr utils module&lt;br/&gt;pub mod nostr_utils;&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    process::ExitCode,&lt;br/&gt;    sync::atomic::{AtomicBool, Ordering},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Parser;&lt;br/&gt;use clap_verbosity_flag::Verbosity;&lt;br/&gt;use tracing::Level;&lt;br/&gt;use tracing_subscriber::{Layer, filter, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;use self::cli::Cli;&lt;br/&gt;&lt;br/&gt;/// Whether the editor is currently open. Prevents logging while the editor is&lt;br/&gt;/// open.&lt;br/&gt;static EDITOR_OPEN: AtomicBool = AtomicBool::new(false);&lt;br/&gt;&lt;br/&gt;/// Configures the logging level based on the provided verbosity.&lt;br/&gt;///&lt;br/&gt;/// When verbosity is set to TRACE, includes file and line numbers in logs.&lt;br/&gt;fn set_log_level(verbosity: Verbosity) {&lt;br/&gt;    let is_trace = verbosity&lt;br/&gt;        .tracing_level()&lt;br/&gt;        .is_some_and(|l| l == tracing::Level::TRACE);&lt;br/&gt;&lt;br/&gt;    let logs_filter = filter::dynamic_filter_fn(move |m, _| {&lt;br/&gt;        // Disable all logs while editor is open&lt;br/&gt;        verbosity.tracing_level().unwrap_or(Level::ERROR) &amp;gt;= *m.level()&lt;br/&gt;            &amp;amp;&amp;amp; !EDITOR_OPEN.load(Ordering::Relaxed)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    let logs_layer = tracing_subscriber::fmt::layer()&lt;br/&gt;        .with_file(is_trace)&lt;br/&gt;        .with_line_number(is_trace)&lt;br/&gt;        .without_time();&lt;br/&gt;    let subscriber = tracing_subscriber::registry().with(logs_layer.with_filter(logs_filter));&lt;br/&gt;    tracing::subscriber::set_global_default(subscriber).ok();&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;// ```rust&lt;br/&gt;// 	#[tokio::main]&lt;br/&gt;// 	async fn main() -&amp;gt; ExitCode {&lt;br/&gt;// 	    let cli = match cli::post_cli(Cli::parse()) {&lt;br/&gt;// 	        Ok(cli) =&amp;gt; cli,&lt;br/&gt;// 	        Err(err) =&amp;gt; {&lt;br/&gt;// 	            eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;// 	            return ExitCode::FAILURE;&lt;br/&gt;// 	        }&lt;br/&gt;// 	    };&lt;br/&gt;// 	&lt;br/&gt;// 	    set_log_level(cli.verbosity);&lt;br/&gt;// 	&lt;br/&gt;// 	    if let Err(err) = cli.run().await {&lt;br/&gt;// 	        tracing::error!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;// 	        return err.exit_code();&lt;br/&gt;// 	    }&lt;br/&gt;// 	&lt;br/&gt;// 	    ExitCode::SUCCESS&lt;br/&gt;// 	}&lt;br/&gt;// ```&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:55:14&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsylhpq06rzhclu249xjw5qmgufe6d4y9jq94wdd3zgakgfl57tc6szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrslum3q6</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsylhpq06rzhclu249xjw5qmgufe6d4y9jq94wdd3zgakgfl57tc6szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrslum3q6" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{net::AddrParseError, process::ExitCode};&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Kind, builder::Error as EventBuilderError},&lt;br/&gt;    signer::SignerError,&lt;br/&gt;};&lt;br/&gt;use nostr_sdk::client::Error as ClientError;&lt;br/&gt;&lt;br/&gt;use crate::cli::ConfigError;&lt;br/&gt;&lt;br/&gt;/// The input data was incorrect in some way. This should only be used for&lt;br/&gt;/// user’s data and not system file.&lt;br/&gt;const DATA_ERROR: u8 = 65;&lt;br/&gt;&lt;br/&gt;/// An internal software error has been detected. This should be limited to&lt;br/&gt;/// non-operating system related errors.&lt;br/&gt;const SOFTWARE_ERROR: u8 = 70;&lt;br/&gt;&lt;br/&gt;/// An error occurred while doing I/O on some file.&lt;br/&gt;const IO_ERROR: u8 = 74;&lt;br/&gt;&lt;br/&gt;/// Something was found in an unconfigured or misconfigured state.&lt;br/&gt;const CONFIG_ERROR: u8 = 78;&lt;br/&gt;&lt;br/&gt;pub type N34Result&amp;lt;T&amp;gt; = Result&amp;lt;T, N34Error&amp;gt;;&lt;br/&gt;&lt;br/&gt;/// N34 errors&lt;br/&gt;#[derive(Debug, thiserror::Error)]&lt;br/&gt;pub enum N34Error {&lt;br/&gt;    #[error(&amp;#34;IO: {0}&amp;#34;)]&lt;br/&gt;    Io(#[from] std::io::Error),&lt;br/&gt;    #[error(&amp;#34;Signer Error: {0}&amp;#34;)]&lt;br/&gt;    SignerError(#[from] SignerError),&lt;br/&gt;    #[error(&amp;#34;Invalid Browser Signer Proxy Address: {0}&amp;#34;)]&lt;br/&gt;    Addr(#[from] AddrParseError),&lt;br/&gt;    #[error(&amp;#34;Browser Signer Proxy Error: {0}&amp;#34;)]&lt;br/&gt;    BrowserSignerProxy(#[from] nostr_browser_signer_proxy::Error),&lt;br/&gt;    #[error(&amp;#34;Keyring error: {0}&amp;#34;)]&lt;br/&gt;    Keyring(#[from] nostr_keyring::Error),&lt;br/&gt;    #[error(&amp;#34;{0}&amp;#34;)]&lt;br/&gt;    Config(#[from] ConfigError),&lt;br/&gt;    #[error(&amp;#34;No editor specified in the `EDITOR` environment variable&amp;#34;)]&lt;br/&gt;    EditorNotFound,&lt;br/&gt;    #[error(&amp;#34;The file you edited is empty. Please save your changes before exiting the editor.&amp;#34;)]&lt;br/&gt;    EmptyEditorFile,&lt;br/&gt;    #[error(&amp;#34;The editor `{0}` exit with unsuccessful exit code `{1}`&amp;#34;)]&lt;br/&gt;    EditorErr(String, i32),&lt;br/&gt;    #[error(&amp;#34;Client Error: {0}&amp;#34;)]&lt;br/&gt;    Client(#[from] ClientError),&lt;br/&gt;    #[error(&amp;#34;Unable to locate the repository. The repository may not exists in the given relays&amp;#34;)]&lt;br/&gt;    NotFoundRepo,&lt;br/&gt;    #[error(&amp;#34;Failed building an event: {0}&amp;#34;)]&lt;br/&gt;    EventBuilder(#[from] EventBuilderError),&lt;br/&gt;    #[error(&amp;#34;Invalid repository id, it can&amp;#39;t be empty and must be kebab-case&amp;#34;)]&lt;br/&gt;    InvalidRepoId,&lt;br/&gt;    #[error(&amp;#34;Invalid event: {0}&amp;#34;)]&lt;br/&gt;    InvalidEvent(String),&lt;br/&gt;    #[error(&amp;#34;Bech32 error: {0}&amp;#34;)]&lt;br/&gt;    Bech32(#[from] nostr::nips::nip19::Error),&lt;br/&gt;    #[error(&amp;#34;Event error: {0}&amp;#34;)]&lt;br/&gt;    Event(#[from] nostr::event::Error),&lt;br/&gt;    #[error(&amp;#34;Event not found in the specified relays&amp;#34;)]&lt;br/&gt;    EventNotFound,&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Can&amp;#39;t reply to this event. Only Git issues, patches, and their comments can be replied \&lt;br/&gt;         to.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    CanNotReplyToEvent,&lt;br/&gt;    #[error(&amp;#34;No repository address given and couldn&amp;#39;t read `nostr-address` file: {0}&amp;#34;)]&lt;br/&gt;    CanNotReadNostrAddressFile(std::io::Error),&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;The `nostr-address` file is empty.  Please add a valid Nostr repository address (naddr) \&lt;br/&gt;         to the file or provide it manually as a flag.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    EmptyNostrAddressFile,&lt;br/&gt;    #[error(&amp;#34;Invalid `nostr-address` file content: {0}&amp;#34;)]&lt;br/&gt;    InvalidNostrAddressFileContent(String),&lt;br/&gt;    #[error(&amp;#34;This command requires at least one relay, but none were provided&amp;#34;)]&lt;br/&gt;    EmptyRelays,&lt;br/&gt;    #[error(&amp;#34;One naddr is required for this command&amp;#34;)]&lt;br/&gt;    EmptyNaddrs,&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;This command requires a signer to sign events. Use `--secret-key`, `--nip07` or \&lt;br/&gt;         `--bunker-url` to provide a signer&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    SignerRequired,&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Invalid repository address. Expected one of these formats:\n- NIP-05 identifier with \&lt;br/&gt;         repository ID: `&amp;lt;user@domain.com&amp;gt;/&amp;lt;repo_id&amp;gt;`\n- Valid NIP-19 naddr string (starts with \&lt;br/&gt;         &amp;#39;naddr1...&amp;#39;)\n- Existing set name (merges all repositories in set)\nError: No set named \&lt;br/&gt;         &amp;#39;{0}&amp;#39; exists.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    InvalidNaddrArg(String),&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Invalid relays. Expected a relay url or a set name that contains some relays\nError: No \&lt;br/&gt;         set named &amp;#39;{0}&amp;#39; exists.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    InvalidRelaysArg(String),&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;The set &amp;#39;{0}&amp;#39; doesn&amp;#39;t contain any addresses. Use &amp;#39;sets update&amp;#39; to add addresses to it.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    EmptySetNaddrs(String),&lt;br/&gt;    #[error(&amp;#34;The set &amp;#39;{0}&amp;#39; doesn&amp;#39;t contain any relays. Use &amp;#39;sets update&amp;#39; to add relays to it.&amp;#34;)]&lt;br/&gt;    EmptySetRelays(String),&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Issue not found, make sure it is in the relays and make sure that the ID is an issue ID&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    CanNotFoundIssue,&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Patch not found, make sure it is in the relays and make sure that the ID is an patch ID&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    CanNotFoundPatch,&lt;br/&gt;    #[error(r#&amp;#34;The given patch id is not a root patch. It must contains `[&amp;#34;t&amp;#34;, &amp;#34;root&amp;#34;]` tag&amp;#34;#)]&lt;br/&gt;    NotRootPatch,&lt;br/&gt;    #[error(&amp;#34;This status kind can&amp;#39;t be set for an issue: {0}&amp;#34;)]&lt;br/&gt;    InvalidIssueStatus(Kind),&lt;br/&gt;    #[error(&amp;#34;This status kind can&amp;#39;t be set for a patch: {0}&amp;#34;)]&lt;br/&gt;    InvalidPatchStatus(Kind),&lt;br/&gt;    #[error(&amp;#34;Can&amp;#39;t find the root patch of the given patch-revision&amp;#34;)]&lt;br/&gt;    RevisionRootNotFound,&lt;br/&gt;    #[error(&amp;#34;Invalid status for the issue/patch: {0}&amp;#34;)]&lt;br/&gt;    InvalidStatus(String),&lt;br/&gt;    #[error(&amp;#34;Not valid bunker URL&amp;#34;)]&lt;br/&gt;    NotBunkerUrl,&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;No secret key found in the keyring. Please use the secret key at least once while \&lt;br/&gt;         keyring is enabled to store it&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    SecretKeyKeyringWithoutEntry,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl N34Error {&lt;br/&gt;    /// Returns the exit code associated with this error&lt;br/&gt;    pub fn exit_code(&amp;amp;self) -&amp;gt; ExitCode {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Io(_) | Self::CanNotReadNostrAddressFile(_) =&amp;gt; ExitCode::from(IO_ERROR),&lt;br/&gt;            Self::Config(_) =&amp;gt; ExitCode::from(CONFIG_ERROR),&lt;br/&gt;            Self::EditorErr(..) =&amp;gt; ExitCode::from(SOFTWARE_ERROR),&lt;br/&gt;            Self::InvalidRepoId&lt;br/&gt;            | Self::EmptyNostrAddressFile&lt;br/&gt;            | Self::InvalidNostrAddressFileContent(_)&lt;br/&gt;            | Self::EmptyRelays&lt;br/&gt;            | Self::EmptyNaddrs&lt;br/&gt;            | Self::SignerRequired&lt;br/&gt;            | Self::InvalidNaddrArg(_)&lt;br/&gt;            | Self::InvalidRelaysArg(_)&lt;br/&gt;            | Self::EmptySetNaddrs(_)&lt;br/&gt;            | Self::EmptySetRelays(_)&lt;br/&gt;            | Self::NotRootPatch =&amp;gt; ExitCode::from(DATA_ERROR),&lt;br/&gt;            _ =&amp;gt; ExitCode::FAILURE,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:55:03&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsd353hkrlz53ncn4wkusgx7s5mp987y27erfdx6sfkz90mpm5l3gszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszexyu0</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsd353hkrlz53ncn4wkusgx7s5mp987y27erfdx6sfkz90mpm5l3gszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszexyu0" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{EventId, Kind},&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::Coordinate,&lt;br/&gt;        nip05::{Nip05Address, Nip05Profile},&lt;br/&gt;        nip19::{self, FromBech32, Nip19Coordinate},&lt;br/&gt;    },&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;    util::BoxedFuture,&lt;br/&gt;};&lt;br/&gt;use nostr_connect::client::AuthUrlHandler;&lt;br/&gt;use tokio::runtime::Handle;&lt;br/&gt;&lt;br/&gt;use super::parsers;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{RepoRelaySet, traits::RepoRelaySetsExt},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Either a NIP-19 coordinate (naddr) or a named set.&lt;br/&gt;#[derive(Debug, Clone)]&lt;br/&gt;pub enum NaddrOrSet {&lt;br/&gt;    /// NIP-19 coordinate.&lt;br/&gt;    Naddr(Nip19Coordinate),&lt;br/&gt;    /// Name of a set (may not exist).&lt;br/&gt;    Set(String),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Either relay URL or a named set.&lt;br/&gt;#[derive(Debug, Clone)]&lt;br/&gt;pub enum RelayOrSet {&lt;br/&gt;    /// Relay URL.&lt;br/&gt;    Relay(RelayUrl),&lt;br/&gt;    /// Name of a set (may not exist).&lt;br/&gt;    Set(String),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Parses and represents a Nostr `nevent1` or `note1`.&lt;br/&gt;#[derive(Debug, Clone)]&lt;br/&gt;pub struct NostrEvent {&lt;br/&gt;    /// Unique identifier for the event.&lt;br/&gt;    pub event_id: EventId,&lt;br/&gt;    /// List of relay URLs associated with the event. Empty if parsing a&lt;br/&gt;    /// `note1`.&lt;br/&gt;    pub relays:   Vec&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[derive(Debug)]&lt;br/&gt;pub struct EchoAuthUrl;&lt;br/&gt;&lt;br/&gt;impl AuthUrlHandler for EchoAuthUrl {&lt;br/&gt;    fn on_auth_url(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        auth_url: nostr::Url,&lt;br/&gt;    ) -&amp;gt; BoxedFuture&amp;lt;&amp;#39;_, Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt;&amp;gt; {&lt;br/&gt;        Box::pin(async move {&lt;br/&gt;            println!(&amp;#34;The bunker requires authentication. Please open this URL: {auth_url}&amp;#34;);&lt;br/&gt;            Ok(())&lt;br/&gt;        })&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl NaddrOrSet {&lt;br/&gt;    /// Returns the naddr if `Naddr` or try to get the relays from the set.&lt;br/&gt;    /// Returns error if the set naddrs are empty or the set not found.&lt;br/&gt;    pub fn get_naddrs(self, sets: &amp;amp;[RepoRelaySet]) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Naddr(nip19_coordinate) =&amp;gt; Ok(vec![nip19_coordinate]),&lt;br/&gt;            Self::Set(name) =&amp;gt; {&lt;br/&gt;                let set = sets&lt;br/&gt;                    .get_set(&amp;amp;name)&lt;br/&gt;                    .map_err(|_| N34Error::InvalidNaddrArg(name.clone()))?;&lt;br/&gt;                if set.naddrs.is_empty() {&lt;br/&gt;                    Err(N34Error::EmptySetNaddrs(name))&lt;br/&gt;                } else {&lt;br/&gt;                    Ok(Vec::from_iter(set.naddrs.clone()))&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl RelayOrSet {&lt;br/&gt;    /// Returns the relay if `Relay` or try to get the relays from the set.&lt;br/&gt;    /// Returns error if the set relays are empty or the set not found&lt;br/&gt;    pub fn get_relays(self, sets: &amp;amp;[RepoRelaySet]) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;RelayUrl&amp;gt;&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Relay(relay) =&amp;gt; Ok(vec![relay]),&lt;br/&gt;            Self::Set(name) =&amp;gt; {&lt;br/&gt;                let set = sets&lt;br/&gt;                    .get_set(&amp;amp;name)&lt;br/&gt;                    .map_err(|_| N34Error::InvalidRelaysArg(name.clone()))?;&lt;br/&gt;                if set.relays.is_empty() {&lt;br/&gt;                    Err(N34Error::EmptySetRelays(name))&lt;br/&gt;                } else {&lt;br/&gt;                    Ok(Vec::from_iter(set.relays.clone()))&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl NostrEvent {&lt;br/&gt;    /// Create a new [`NostrEvent`] instance&lt;br/&gt;    fn new(event_id: EventId, relays: Vec&amp;lt;RelayUrl&amp;gt;) -&amp;gt; Self {&lt;br/&gt;        Self { event_id, relays }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl FromStr for NaddrOrSet {&lt;br/&gt;    type Err = String;&lt;br/&gt;&lt;br/&gt;    /// Parses a Git repository address which can be either:&lt;br/&gt;    /// - A bech32-encoded naddr (e.g. &amp;#34;naddr1...&amp;#34;) for Git repository&lt;br/&gt;    ///   announcements (kind 30617)&lt;br/&gt;    /// - A NIP-05 identifier with repository ID (e.g. &amp;#34;4rs.nl/n34&amp;#34; or&lt;br/&gt;    ///   &amp;#34;_@4rs.nl/n34&amp;#34;)&lt;br/&gt;    /// - A set name.&lt;br/&gt;    ///&lt;br/&gt;    /// Returns an error for invalid formats, failed bech32 decoding, wrong&lt;br/&gt;    /// event kind.&lt;br/&gt;    fn from_str(naddr_or_set: &amp;amp;str) -&amp;gt; Result&amp;lt;Self, Self::Err&amp;gt; {&lt;br/&gt;        let naddr_or_set = naddr_or_set.trim();&lt;br/&gt;&lt;br/&gt;        if naddr_or_set.contains(&amp;#34;/&amp;#34;) {&lt;br/&gt;            let (nip5, repo_id) = naddr_or_set.split_once(&amp;#34;/&amp;#34;).expect(&amp;#34;There is a `/`&amp;#34;);&lt;br/&gt;            parse_nip5_repo(nip5, repo_id)&lt;br/&gt;        } else if naddr_or_set.starts_with(&amp;#34;naddr1&amp;#34;) || naddr_or_set.starts_with(&amp;#34;nostr:naddr1&amp;#34;) {&lt;br/&gt;            parsers::parse_repo_naddr(naddr_or_set.trim_start_matches(&amp;#34;nostr:&amp;#34;)).map(Self::Naddr)&lt;br/&gt;        } else {&lt;br/&gt;            Ok(Self::Set(naddr_or_set.to_owned()))&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl FromStr for RelayOrSet {&lt;br/&gt;    type Err = String;&lt;br/&gt;&lt;br/&gt;    /// Parse a string into a relay URL or a set name.&lt;br/&gt;    /// If the string is a valid URL (e.g., &amp;#34;wss://example.com&amp;#34;), it&amp;#39;s treated&lt;br/&gt;    /// as a relay URL. Otherwise, it&amp;#39;s treated as a set name, and its&lt;br/&gt;    /// associated relays will be merged.&lt;br/&gt;    fn from_str(relay_or_set: &amp;amp;str) -&amp;gt; Result&amp;lt;Self, Self::Err&amp;gt; {&lt;br/&gt;        let relay_or_set = relay_or_set.trim();&lt;br/&gt;&lt;br/&gt;        if relay_or_set.starts_with(&amp;#34;wss://&amp;#34;) {&lt;br/&gt;            RelayUrl::from_str(relay_or_set)&lt;br/&gt;                .map_err(|err| err.to_string())&lt;br/&gt;                .map(Self::Relay)&lt;br/&gt;        } else {&lt;br/&gt;            Ok(Self::Set(relay_or_set.to_owned()))&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl FromStr for NostrEvent {&lt;br/&gt;    type Err = String;&lt;br/&gt;&lt;br/&gt;    fn from_str(s: &amp;amp;str) -&amp;gt; Result&amp;lt;Self, Self::Err&amp;gt; {&lt;br/&gt;        let str_event = s.trim().trim_start_matches(&amp;#34;nostr:&amp;#34;);&lt;br/&gt;        if str_event.starts_with(&amp;#34;nevent1&amp;#34;) {&lt;br/&gt;            let event = nip19::Nip19Event::from_bech32(str_event).map_err(|e| e.to_string())?;&lt;br/&gt;            Ok(Self::new(event.event_id, event.relays))&lt;br/&gt;        } else if str_event.starts_with(&amp;#34;note1&amp;#34;) {&lt;br/&gt;            Ok(Self::new(&lt;br/&gt;                EventId::from_bech32(str_event).map_err(|e| e.to_string())?,&lt;br/&gt;                Vec::new(),&lt;br/&gt;            ))&lt;br/&gt;        } else {&lt;br/&gt;            Err(&amp;#34;Invalid event id, must starts with `note1` or `nevent1`&amp;#34;.to_owned())&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;fn parse_nip5_repo(nip5: &amp;amp;str, repo_id: &amp;amp;str) -&amp;gt; Result&amp;lt;NaddrOrSet, String&amp;gt; {&lt;br/&gt;    let (username, domain) = nip5.split_once(&amp;#34;@&amp;#34;).unwrap_or((&amp;#34;_&amp;#34;, nip5));&lt;br/&gt;&lt;br/&gt;    let nip5_address =&lt;br/&gt;        Nip05Address::parse(&amp;amp;format!(&amp;#34;{username}@{domain}&amp;#34;)).map_err(|err| err.to_string())?;&lt;br/&gt;&lt;br/&gt;    let nip5_json = tokio::task::block_in_place(|| {&lt;br/&gt;        Handle::current().block_on(async {&lt;br/&gt;            reqwest::get(nip5_address.url().as_str())&lt;br/&gt;                .await&lt;br/&gt;                .map_err(|err| err.to_string())?&lt;br/&gt;                .text()&lt;br/&gt;                .await&lt;br/&gt;                .map_err(|err| err.to_string())&lt;br/&gt;        })&lt;br/&gt;    })?;&lt;br/&gt;&lt;br/&gt;    let nip5_profile =&lt;br/&gt;        Nip05Profile::from_raw_json(&amp;amp;nip5_address, &amp;amp;nip5_json).map_err(|err| err.to_string())?;&lt;br/&gt;&lt;br/&gt;    Ok(NaddrOrSet::Naddr(Nip19Coordinate::new(&lt;br/&gt;        Coordinate::new(Kind::GitRepoAnnouncement, nip5_profile.public_key).identifier(repo_id),&lt;br/&gt;        nip5_profile.relays,&lt;br/&gt;    )))&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:54:52&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspwkyq8kcte9rl2dx6g944nljj2amf2kv548tkpmy9mx5ajq5d67czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsh2egqe</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspwkyq8kcte9rl2dx6g944nljj2amf2kv548tkpmy9mx5ajq5d67czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsh2egqe" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use nostr::{event::EventId, nips::nip19::Nip19Coordinate, types::RelayUrl};&lt;br/&gt;&lt;br/&gt;use super::CliOptions;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        ConfigError,&lt;br/&gt;        RepoRelaySet,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent, RelayOrSet},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// A trait defining the interface for command runners in the CLI.&lt;br/&gt;pub trait CommandRunner {&lt;br/&gt;    /// Whether this command needs the relays option (false by default).&lt;br/&gt;    /// Only applies to commands, not subcommands.&lt;br/&gt;    const NEED_RELAYS: bool = false;&lt;br/&gt;    /// Indicates if this command requires the signer. Defaults to true.&lt;br/&gt;    /// Only applies to commands, not subcommands.&lt;br/&gt;    const NEED_SIGNER: bool = true;&lt;br/&gt;&lt;br/&gt;    /// Executes the command and returns a Result indicating success or failure.&lt;br/&gt;    fn run(self, options: CliOptions) -&amp;gt; impl Future&amp;lt;Output = N34Result&amp;lt;()&amp;gt;&amp;gt; &#43; Send;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(VecNostrEventExt)]&lt;br/&gt;impl Vec&amp;lt;NostrEvent&amp;gt; {&lt;br/&gt;    /// Extracts `EventId` from each `NostrEvent` and collects them into a&lt;br/&gt;    /// `Vec&amp;lt;EventId&amp;gt;`.&lt;br/&gt;    pub fn into_event_ids(self) -&amp;gt; Vec&amp;lt;EventId&amp;gt; {&lt;br/&gt;        self.into_iter().map(|e| e.event_id).collect()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(NaddrOrSetVecExt)]&lt;br/&gt;impl Vec&amp;lt;NaddrOrSet&amp;gt; {&lt;br/&gt;    /// Converts this vector of [`NaddrOrSet`] into a flat vector of&lt;br/&gt;    /// [`Nip19Coordinate`] using the given sets.&lt;br/&gt;    pub fn flat_naddrs(self, sets: &amp;amp;[RepoRelaySet]) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;        self.into_iter()&lt;br/&gt;            .map(|n| n.get_naddrs(sets))&lt;br/&gt;            .try_fold(Vec::new(), |mut acc, item| {&lt;br/&gt;                acc.extend(item?);&lt;br/&gt;                Ok(acc)&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(RelayOrSetVecExt)]&lt;br/&gt;impl Vec&amp;lt;RelayOrSet&amp;gt; {&lt;br/&gt;    /// Converts this vector of [`RelayOrSet`] into a flat vector of&lt;br/&gt;    /// [`RelayUrl`] using the given sets.&lt;br/&gt;    pub fn flat_relays(self, sets: &amp;amp;[RepoRelaySet]) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;RelayUrl&amp;gt;&amp;gt; {&lt;br/&gt;        self.into_iter()&lt;br/&gt;            .map(|n| n.get_relays(sets))&lt;br/&gt;            .try_fold(Vec::new(), |mut acc, item| {&lt;br/&gt;                acc.extend(item?);&lt;br/&gt;                Ok(acc)&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(OptionNaddrOrSetVecExt)]&lt;br/&gt;impl Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt; {&lt;br/&gt;    /// Converts this vector of [`NaddrOrSet`] into a flat vector of&lt;br/&gt;    /// [`Nip19Coordinate`] using the given sets.&lt;br/&gt;    pub fn flat_naddrs(&amp;amp;self, sets: &amp;amp;[RepoRelaySet]) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt;&amp;gt; {&lt;br/&gt;        // Clones self here to simplify command code&lt;br/&gt;        self.clone()&lt;br/&gt;            .map(|naddrs| naddrs.flat_naddrs(sets))&lt;br/&gt;            .transpose()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(MutRepoRelaySetsExt)]&lt;br/&gt;impl Vec&amp;lt;RepoRelaySet&amp;gt; {&lt;br/&gt;    /// Removes duplicate repository addresses from each set.&lt;br/&gt;    ///&lt;br/&gt;    /// Relays are automatically deduplicated by the HashSet, but&lt;br/&gt;    /// repository addresses may appear duplicated if relays are sorted&lt;br/&gt;    /// differently or when relay counts vary. This compares addresses by&lt;br/&gt;    /// their coordinates, ignoring any embedded relay details.&lt;br/&gt;    pub fn dedup_naddrs(&amp;amp;mut self) {&lt;br/&gt;        self.iter_mut().for_each(RepoRelaySet::dedup_naddrs);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds and returns a mutable reference a set with the given name. Returns&lt;br/&gt;    /// an error if no set with this name exists.&lt;br/&gt;    pub fn get_mut_set(&amp;amp;mut self, name: impl AsRef&amp;lt;str&amp;gt;) -&amp;gt; N34Result&amp;lt;&amp;amp;mut RepoRelaySet&amp;gt; {&lt;br/&gt;        let name = name.as_ref();&lt;br/&gt;        let set = self&lt;br/&gt;            .iter_mut()&lt;br/&gt;            .find(|set| set.name == name)&lt;br/&gt;            .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?;&lt;br/&gt;&lt;br/&gt;        tracing::trace!(&lt;br/&gt;            name = %name, set = ?set,&lt;br/&gt;            &amp;#34;Successfully located a set with the giving name&amp;#34;&lt;br/&gt;        );&lt;br/&gt;&lt;br/&gt;        Ok(set)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Creates and pushes a new set with the given name.&lt;br/&gt;    ///&lt;br/&gt;    /// Returns an error if a set with the same name already exists.&lt;br/&gt;    pub fn push_set(&lt;br/&gt;        &amp;amp;mut self,&lt;br/&gt;        name: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;        repos: impl IntoIterator&amp;lt;Item = Nip19Coordinate&amp;gt;,&lt;br/&gt;        relays: impl IntoIterator&amp;lt;Item = RelayUrl&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let set_name: String = name.into();&lt;br/&gt;        tracing::trace!(sets = ?self, &amp;#34;Pushing set &amp;#39;{set_name}&amp;#39; to sets collection&amp;#34;);&lt;br/&gt;&lt;br/&gt;        if self.as_slice().exists(&amp;amp;set_name) {&lt;br/&gt;            return Err(ConfigError::SetDuplicateName(set_name).into());&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        self.push(RepoRelaySet::new(set_name, repos, relays));&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Removes the set with the given name if it exists. Returns an error if&lt;br/&gt;    /// the set is not found.&lt;br/&gt;    pub fn remove_set(&amp;amp;mut self, name: impl Into&amp;lt;String&amp;gt;) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let set_name: String = name.into();&lt;br/&gt;        tracing::trace!(set_name, sets = ?self, &amp;#34;Removing set &amp;#39;{set_name}&amp;#39; from sets collection&amp;#34;);&lt;br/&gt;&lt;br/&gt;        if !self.as_slice().exists(&amp;amp;set_name) {&lt;br/&gt;            return Err(ConfigError::SetNotFound(set_name).into());&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        self.retain(|s| s.name != set_name);&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Removes the given relays from the specified set.&lt;br/&gt;    pub fn remove_relays(&lt;br/&gt;        &amp;amp;mut self,&lt;br/&gt;        name: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;        relays: impl Iterator&amp;lt;Item = RelayUrl&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let relays = Vec::from_iter(relays);&lt;br/&gt;        let set = self.get_mut_set(name.into())?;&lt;br/&gt;&lt;br/&gt;        set.relays.retain(|r| !relays.contains(r));&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Removes the given naddrs from the specified set.&lt;br/&gt;    pub fn remove_naddrs(&lt;br/&gt;        &amp;amp;mut self,&lt;br/&gt;        name: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;        naddrs: impl Iterator&amp;lt;Item = Nip19Coordinate&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let coordinates = Vec::from_iter(naddrs.map(|n| n.coordinate));&lt;br/&gt;        let set = self.get_mut_set(name.into())?;&lt;br/&gt;&lt;br/&gt;        set.naddrs.retain(|n| !coordinates.contains(&amp;amp;n.coordinate));&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(RepoRelaySetsExt)]&lt;br/&gt;impl &amp;amp;[RepoRelaySet] {&lt;br/&gt;    /// Checks for duplicate set names. Returns an error if any duplicates are&lt;br/&gt;    /// found.&lt;br/&gt;    pub fn ensure_names(&amp;amp;self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let mut names = Vec::with_capacity(self.len());&lt;br/&gt;        names.extend(self.iter().map(|s| s.name.to_owned()));&lt;br/&gt;&lt;br/&gt;        names.sort_unstable();&lt;br/&gt;&lt;br/&gt;        if let Some(duplicate) = duplicate_in_sorted(&amp;amp;names) {&lt;br/&gt;            return Err(ConfigError::SetDuplicateName(duplicate.clone()).into());&lt;br/&gt;        }&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if a set with the given name exists.&lt;br/&gt;    pub fn exists(&amp;amp;self, set_name: &amp;amp;str) -&amp;gt; bool {&lt;br/&gt;        self.iter().any(|set| set.name == set_name)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds and returns a reference a set with the given name. Returns an&lt;br/&gt;    /// error if no set with this name exists.&lt;br/&gt;    pub fn get_set(&amp;amp;self, name: impl AsRef&amp;lt;str&amp;gt;) -&amp;gt; N34Result&amp;lt;&amp;amp;RepoRelaySet&amp;gt; {&lt;br/&gt;        let name = name.as_ref();&lt;br/&gt;        let set = self&lt;br/&gt;            .iter()&lt;br/&gt;            .find(|set| set.name == name)&lt;br/&gt;            .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?;&lt;br/&gt;        tracing::trace!(&lt;br/&gt;            name = %name, set = ?set,&lt;br/&gt;            &amp;#34;Successfully located a set with the giving name&amp;#34;&lt;br/&gt;        );&lt;br/&gt;        Ok(set)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Helper function that checks for duplicates in a sorted slice&lt;br/&gt;fn duplicate_in_sorted&amp;lt;T: PartialEq &#43; Clone&amp;gt;(items: &amp;amp;[T]) -&amp;gt; Option&amp;lt;&amp;amp;T&amp;gt; {&lt;br/&gt;    items.windows(2).find(|w| w[0] == w[1]).map(|w| &amp;amp;w[0])&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:54:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsr36n5u8jsxp49rvn58p42cmlz7ply0u2ymgq02rl4q9ag2p8fc4czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrscfnxz0</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsr36n5u8jsxp49rvn58p42cmlz7ply0u2ymgq02rl4q9ag2p8fc4czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrscfnxz0" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    collections::HashSet,&lt;br/&gt;    fs,&lt;br/&gt;    path::{Path, PathBuf},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    Kind,&lt;br/&gt;    nips::{&lt;br/&gt;        nip19::{FromBech32, Nip19Coordinate, ToBech32},&lt;br/&gt;        nip46::NostrConnectURI,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;use serde::{Deserialize, Serialize, Serializer};&lt;br/&gt;&lt;br/&gt;use super::CliConfig;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::DEFAULT_FALLBACK_PATH,&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;pub fn parse_repo_naddr(repo_naddr: &amp;amp;str) -&amp;gt; Result&amp;lt;Nip19Coordinate, String&amp;gt; {&lt;br/&gt;    let naddr = Nip19Coordinate::from_bech32(repo_naddr).map_err(|err| err.to_string())?;&lt;br/&gt;    if naddr.relays.is_empty() {&lt;br/&gt;        tracing::warn!(&amp;#34;The repository naddr does not contain any relay hints&amp;#34;);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    (naddr.kind == Kind::GitRepoAnnouncement)&lt;br/&gt;        .then_some(naddr)&lt;br/&gt;        .ok_or_else(|| &amp;#34;Invalid naddr: must be of kind 30617 (GitRepoAnnouncement)&amp;#34;.to_owned())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Parses a nostr-address file into a NIP-19 coordinates. Expects the file to&lt;br/&gt;/// contain a repository announcements.&lt;br/&gt;pub fn parse_nostr_address_file(file_path: &amp;amp;Path) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    let addresses = fs::read_to_string(file_path)&lt;br/&gt;        .map_err(N34Error::CanNotReadNostrAddressFile)?&lt;br/&gt;        .split(&amp;#34;\n&amp;#34;)&lt;br/&gt;        .filter_map(|line| {&lt;br/&gt;            (!line.starts_with(&amp;#34;#&amp;#34;) &amp;amp;&amp;amp; !line.trim().is_empty())&lt;br/&gt;                .then_some(parse_repo_naddr(line).map_err(N34Error::InvalidNostrAddressFileContent))&lt;br/&gt;        })&lt;br/&gt;        .collect::&amp;lt;N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt;&amp;gt;()?;&lt;br/&gt;    if addresses.is_empty() {&lt;br/&gt;        return Err(N34Error::EmptyNostrAddressFile);&lt;br/&gt;    }&lt;br/&gt;    Ok(addresses)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Loads CLI configuration from given path. Uses default config path if input&lt;br/&gt;/// matches fallback path.&lt;br/&gt;pub fn parse_config_path(config_path: &amp;amp;str) -&amp;gt; N34Result&amp;lt;CliConfig&amp;gt; {&lt;br/&gt;    let mut path = PathBuf::from(config_path.trim());&lt;br/&gt;&lt;br/&gt;    if config_path == DEFAULT_FALLBACK_PATH {&lt;br/&gt;        path = super::defaults::config_path()?;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    CliConfig::load(path)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Parses a bunker URL and checks if it&amp;#39;s a valid Nostr Connect URI.&lt;br/&gt;/// Returns an error if the URL is not a valid bunker URL.&lt;br/&gt;pub fn parse_bunker_url(bunker_url: &amp;amp;str) -&amp;gt; N34Result&amp;lt;NostrConnectURI&amp;gt; {&lt;br/&gt;    match NostrConnectURI::parse(bunker_url) {&lt;br/&gt;        Ok(url) if url.is_bunker() =&amp;gt; Ok(url),&lt;br/&gt;        _ =&amp;gt; Err(N34Error::NotBunkerUrl),&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Serializes a set of NIP-19 coordinates as a list of bech32 strings.&lt;br/&gt;pub fn ser_naddrs&amp;lt;S&amp;gt;(naddr: &amp;amp;HashSet&amp;lt;Nip19Coordinate&amp;gt;, serializer: S) -&amp;gt; Result&amp;lt;S::Ok, S::Error&amp;gt;&lt;br/&gt;where&lt;br/&gt;    S: Serializer,&lt;br/&gt;{&lt;br/&gt;    let str_naddrs = naddr&lt;br/&gt;        .iter()&lt;br/&gt;        .map(|n| n.to_bech32().map_err(|err| err.to_string()))&lt;br/&gt;        .collect::&amp;lt;Result&amp;lt;Vec&amp;lt;_&amp;gt;, _&amp;gt;&amp;gt;()&lt;br/&gt;        .map_err(serde::ser::Error::custom)?;&lt;br/&gt;&lt;br/&gt;    str_naddrs.serialize(serializer)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Deserializes a list of bech32 strings into a set of NIP-19 coordinates.&lt;br/&gt;pub fn de_naddrs&amp;lt;&amp;#39;de, D&amp;gt;(deserializer: D) -&amp;gt; Result&amp;lt;HashSet&amp;lt;Nip19Coordinate&amp;gt;, D::Error&amp;gt;&lt;br/&gt;where&lt;br/&gt;    D: serde::Deserializer&amp;lt;&amp;#39;de&amp;gt;,&lt;br/&gt;{&lt;br/&gt;    Vec::&amp;lt;String&amp;gt;::deserialize(deserializer)?&lt;br/&gt;        .into_iter()&lt;br/&gt;        .map(|naddr| Nip19Coordinate::from_bech32(&amp;amp;naddr))&lt;br/&gt;        .collect::&amp;lt;Result&amp;lt;HashSet&amp;lt;_&amp;gt;, _&amp;gt;&amp;gt;()&lt;br/&gt;        .map_err(serde::de::Error::custom)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn ser_bunker_url&amp;lt;S&amp;gt;(&lt;br/&gt;    bunker_url: &amp;amp;Option&amp;lt;NostrConnectURI&amp;gt;,&lt;br/&gt;    serializer: S,&lt;br/&gt;) -&amp;gt; Result&amp;lt;S::Ok, S::Error&amp;gt;&lt;br/&gt;where&lt;br/&gt;    S: Serializer,&lt;br/&gt;{&lt;br/&gt;    bunker_url&lt;br/&gt;        .as_ref()&lt;br/&gt;        .map(|u| u.to_string())&lt;br/&gt;        .serialize(serializer)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn de_bunker_url&amp;lt;&amp;#39;de, D&amp;gt;(deserializer: D) -&amp;gt; Result&amp;lt;Option&amp;lt;NostrConnectURI&amp;gt;, D::Error&amp;gt;&lt;br/&gt;where&lt;br/&gt;    D: serde::Deserializer&amp;lt;&amp;#39;de&amp;gt;,&lt;br/&gt;{&lt;br/&gt;    Option::&amp;lt;String&amp;gt;::deserialize(deserializer)?&lt;br/&gt;        .map(|u| parse_bunker_url(&amp;amp;u))&lt;br/&gt;        .transpose()&lt;br/&gt;        .map_err(serde::de::Error::custom)&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:54:29&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfkxvx5ee6p8qm0unu6623eckh54z3lh867am4lfz02u76yqmz2mczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsj7y0vf</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfkxvx5ee6p8qm0unu6623eckh54z3lh867am4lfz02u76yqmz2mczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsj7y0vf" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    net::{Ipv4Addr, SocketAddr, SocketAddrV4},&lt;br/&gt;    time::Duration,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use nostr_browser_signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions};&lt;br/&gt;&lt;br/&gt;/// The default socket address used for the NIP-07 signer proxy, set to&lt;br/&gt;/// localhost on port 51034.&lt;br/&gt;pub const DEFAULT_NIP07_PROXY_ADDR: SocketAddr =&lt;br/&gt;    SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 51034));&lt;br/&gt;&lt;br/&gt;/// How long to wait for the proxy response (3 minutes).&lt;br/&gt;pub const BROWSER_SIGNER_PROXY_TIMEOUT: Duration = Duration::from_secs(60 * 3);&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Represents the state used for CLI options.&lt;br/&gt;pub struct OptionsState {&lt;br/&gt;    /// The browser signer proxy, will be used if `--nip07` is enabled&lt;br/&gt;    pub browser_signer_proxy: BrowserSignerProxy,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl Default for OptionsState {&lt;br/&gt;    fn default() -&amp;gt; Self {&lt;br/&gt;        Self {&lt;br/&gt;            browser_signer_proxy: default_browser_signer_proxy(),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Build the default browser signer proxy&lt;br/&gt;#[inline]&lt;br/&gt;fn default_browser_signer_proxy() -&amp;gt; BrowserSignerProxy {&lt;br/&gt;    BrowserSignerProxy::new(&lt;br/&gt;        BrowserSignerProxyOptions::default()&lt;br/&gt;            .timeout(BROWSER_SIGNER_PROXY_TIMEOUT)&lt;br/&gt;            .ip_addr(DEFAULT_NIP07_PROXY_ADDR.ip())&lt;br/&gt;            .port(DEFAULT_NIP07_PROXY_ADDR.port()),&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:54:18&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdky9ctj5fsmgfzxwncq9xktesg5zrcw2js9ft6elmtvf7cwp3uaczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvgpx62</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdky9ctj5fsmgfzxwncq9xktesg5zrcw2js9ft6elmtvf7cwp3uaczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvgpx62" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Commands module&lt;br/&gt;pub mod commands;&lt;br/&gt;/// Common commands used by multiply commands&lt;br/&gt;pub mod common_commands;&lt;br/&gt;/// The CLI config&lt;br/&gt;pub mod config;&lt;br/&gt;/// Default lazy values for CLI arguments&lt;br/&gt;pub mod defaults;&lt;br/&gt;/// Macros for CLI application.&lt;br/&gt;pub mod macros;&lt;br/&gt;/// Represents the state used for CLI options.&lt;br/&gt;pub mod options_state;&lt;br/&gt;/// CLI arguments parsers&lt;br/&gt;pub mod parsers;&lt;br/&gt;/// CLI traits&lt;br/&gt;pub mod traits;&lt;br/&gt;/// Common helper types used throughout the CLI.&lt;br/&gt;pub mod types;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;use clap::Parser;&lt;br/&gt;use clap_verbosity_flag::Verbosity;&lt;br/&gt;use nostr::key::Keys;&lt;br/&gt;use nostr::key::SecretKey;&lt;br/&gt;use nostr_browser_signer_proxy::BrowserSignerProxy;&lt;br/&gt;use nostr_browser_signer_proxy::BrowserSignerProxyOptions;&lt;br/&gt;use nostr_keyring::KeyringError;&lt;br/&gt;use nostr_keyring::NostrKeyring;&lt;br/&gt;use types::RelayOrSet;&lt;br/&gt;&lt;br/&gt;pub use self::commands::*;&lt;br/&gt;pub use self::config::*;&lt;br/&gt;use self::traits::CommandRunner;&lt;br/&gt;use crate::cli::options_state::BROWSER_SIGNER_PROXY_TIMEOUT;&lt;br/&gt;use crate::error::N34Error;&lt;br/&gt;use crate::error::N34Result;&lt;br/&gt;use crate::nostr_utils::traits::NostrKeyringErrorUtils;&lt;br/&gt;&lt;br/&gt;/// Header message, used in the help message&lt;br/&gt;const HEADER: &amp;amp;str = r#&amp;#34;Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;License GNU GPL-3.0-or-later &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;&lt;br/&gt;This is free software: you are free to change and redistribute it.&lt;br/&gt;There is NO WARRANTY, to the extent permitted by law.&lt;br/&gt;&lt;br/&gt;Git repository: &lt;a href=&#34;https://git.4rs.nl/awiteb/n34.git&amp;#34;#&#34;&gt;https://git.4rs.nl/awiteb/n34.git&amp;#34;#&lt;/a&gt;;&lt;br/&gt;&lt;br/&gt;/// Footer message, used in the help message&lt;br/&gt;const FOOTER: &amp;amp;str = r#&amp;#34;Please report bugs to &amp;lt;naddr1qqpkuve5qgsqqqqqq9g9uljgjfcyd6dm4fegk8em2yfz0c3qp3tc6mntkrrhawgrqsqqqauesksc39&amp;gt;.&amp;#34;#;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Name of the file storing the repository address&lt;br/&gt;pub const NOSTR_ADDRESS_FILE: &amp;amp;str = &amp;#34;nostr-address&amp;#34;;&lt;br/&gt;&lt;br/&gt;#[derive(Parser, Debug)]&lt;br/&gt;#[command(about, version, before_long_help = HEADER, after_long_help = FOOTER)]&lt;br/&gt;/// A command-line interface for interacting with NIP-34 and other Nostr&lt;br/&gt;/// code-related stuff.&lt;br/&gt;pub struct Cli {&lt;br/&gt;    #[command(flatten)]&lt;br/&gt;    pub options:   commands::CliOptions,&lt;br/&gt;    /// Controls the verbosity level of output&lt;br/&gt;    #[command(flatten)]&lt;br/&gt;    pub verbosity: Verbosity,&lt;br/&gt;    /// The subcommand to execute&lt;br/&gt;    #[command(subcommand)]&lt;br/&gt;    pub command:   commands::Commands,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl Cli {&lt;br/&gt;    /// Keyring service name of n34&lt;br/&gt;    pub const N34_KEYRING_SERVICE_NAME: &amp;amp;str = &amp;#34;n34&amp;#34;;&lt;br/&gt;    /// Keyring entry name of the n34 keypair&lt;br/&gt;    pub const N34_KEY_PAIR_ENTRY: &amp;amp;str = &amp;#34;n34_keypair&amp;#34;;&lt;br/&gt;    /// Keyring entry name of the user secret key&lt;br/&gt;    pub const USER_KEY_PAIR_ENTRY: &amp;amp;str = &amp;#34;user_keypair&amp;#34;;&lt;br/&gt;&lt;br/&gt;    /// Executes the command&lt;br/&gt;    pub async fn run(self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        self.command.run(self.options).await&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the n34 keypair from the keyring or generates and stores a new one&lt;br/&gt;    /// if none exists.&lt;br/&gt;    pub fn n34_keypair() -&amp;gt; N34Result&amp;lt;Keys&amp;gt; {&lt;br/&gt;        let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);&lt;br/&gt;&lt;br/&gt;        match keyring.get(Self::N34_KEY_PAIR_ENTRY) {&lt;br/&gt;            Ok(keys) =&amp;gt; Ok(keys),&lt;br/&gt;            Err(nostr_keyring::Error::Keyring(KeyringError::NoEntry)) =&amp;gt; {&lt;br/&gt;                let new_keys = Keys::generate();&lt;br/&gt;                keyring.set(Self::N34_KEY_PAIR_ENTRY, &amp;amp;new_keys)?;&lt;br/&gt;                Ok(new_keys)&lt;br/&gt;            }&lt;br/&gt;            Err(err) =&amp;gt; Err(N34Error::Keyring(err)),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Retrieves the user&amp;#39;s keypair from the keyring. If no key exists and one&lt;br/&gt;    /// is provided, stores and returns it. If no key exists and none is&lt;br/&gt;    /// provided, returns an error.&lt;br/&gt;    pub fn user_keypair(secret_key: Option&amp;lt;SecretKey&amp;gt;) -&amp;gt; N34Result&amp;lt;Keys&amp;gt; {&lt;br/&gt;        let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);&lt;br/&gt;        let keyring_key = keyring.get(Self::USER_KEY_PAIR_ENTRY);&lt;br/&gt;&lt;br/&gt;        if let Err(ref err) = keyring_key&lt;br/&gt;            &amp;amp;&amp;amp; err.is_keyring_no_entry()&lt;br/&gt;            &amp;amp;&amp;amp; let Some(secret_key) = secret_key&lt;br/&gt;        {&lt;br/&gt;            let keypair = Keys::new(secret_key);&lt;br/&gt;            keyring.set(Self::USER_KEY_PAIR_ENTRY, &amp;amp;keypair)?;&lt;br/&gt;            return Ok(keypair);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        keyring_key.map_err(|err| {&lt;br/&gt;            if err.is_keyring_no_entry() {&lt;br/&gt;                N34Error::SecretKeyKeyringWithoutEntry&lt;br/&gt;            } else {&lt;br/&gt;                N34Error::Keyring(err)&lt;br/&gt;            }&lt;br/&gt;        })&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Processes the CLI configuration by applying fallback values from config if&lt;br/&gt;/// needed. Returns the processed Cli configuration if successful.&lt;br/&gt;pub fn post_cli(mut cli: Cli) -&amp;gt; N34Result&amp;lt;Cli&amp;gt; {&lt;br/&gt;    cli.options.pow = cli.options.pow.or(cli.options.config.pow);&lt;br/&gt;&lt;br/&gt;    if cli.options.relays.is_empty()&lt;br/&gt;        &amp;amp;&amp;amp; let Some(relays) = &amp;amp;cli.options.config.fallback_relays&lt;br/&gt;    {&lt;br/&gt;        cli.options.relays = relays.iter().cloned().map(RelayOrSet::Relay).collect();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // Automatically sets the signer based on the configuration if no signer&lt;br/&gt;    // is provided.&lt;br/&gt;    if !cli.options.nip07&lt;br/&gt;        &amp;amp;&amp;amp; cli.options.bunker_url.is_none()&lt;br/&gt;        &amp;amp;&amp;amp; (cli.options.secret_key.is_none() || cli.options.config.keyring_secret_key)&lt;br/&gt;    {&lt;br/&gt;        if let Some(addr) = cli.options.config.nip07 {&lt;br/&gt;            cli.options.nip07 = true;&lt;br/&gt;            cli.options.state.browser_signer_proxy = BrowserSignerProxy::new(&lt;br/&gt;                BrowserSignerProxyOptions::default()&lt;br/&gt;                    .timeout(BROWSER_SIGNER_PROXY_TIMEOUT)&lt;br/&gt;                    .ip_addr(addr.ip())&lt;br/&gt;                    .port(addr.port()),&lt;br/&gt;            );&lt;br/&gt;        } else if let Some(bunker_url) = &amp;amp;cli.options.config.bunker_url {&lt;br/&gt;            cli.options.bunker_url = Some(bunker_url.clone());&lt;br/&gt;        } else if cli.options.config.keyring_secret_key {&lt;br/&gt;            cli.options.secret_key = Some(&lt;br/&gt;                Cli::user_keypair(cli.options.secret_key)?&lt;br/&gt;                    .secret_key()&lt;br/&gt;                    .clone(),&lt;br/&gt;            );&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    Ok(cli)&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:54:07&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsymctullhwnmyr3gwhwv6vzn3uw3a2zyaylu6fpw4mcchqml9j2eczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsc3xspq</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsymctullhwnmyr3gwhwv6vzn3uw3a2zyaylu6fpw4mcchqml9j2eczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsc3xspq" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use super::traits::CommandRunner;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Returns whether the command runner type `T` requires relays.&lt;br/&gt;pub fn get_relays_state&amp;lt;T: CommandRunner&amp;gt;(_v: &amp;amp;T) -&amp;gt; bool {&lt;br/&gt;    T::NEED_RELAYS&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns whether the command runner type `T` requires a signer.&lt;br/&gt;pub fn get_signer_state&amp;lt;T: CommandRunner&amp;gt;(_v: &amp;amp;T) -&amp;gt; bool {&lt;br/&gt;    T::NEED_SIGNER&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Executes a command with required setup checks. The first parameter is the&lt;br/&gt;/// command to match on (often `self`), followed by options. Optional&lt;br/&gt;/// subcommands come next, and commands with arguments (after `&amp;amp;`) are listed&lt;br/&gt;/// last.&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! run_command {&lt;br/&gt;    ($command:ident, $options:ident, $($subcommands:ident)* &amp;amp; $($commands:ident)*) =&amp;gt; {&lt;br/&gt;        match $command {&lt;br/&gt;            $(&lt;br/&gt;                Self::$subcommands { subcommands } =&amp;gt; subcommands.run($options).await,&lt;br/&gt;            )*&lt;br/&gt;            $(&lt;br/&gt;                Self::$commands ( args ) =&amp;gt; {&lt;br/&gt;                    if $crate::cli::macros::get_relays_state(&amp;amp;args) {&lt;br/&gt;                        $options.ensure_relays()?;&lt;br/&gt;                    }&lt;br/&gt;                    if $crate::cli::macros::get_signer_state(&amp;amp;args) {&lt;br/&gt;                        $options.ensure_signer()?;&lt;br/&gt;                    }&lt;br/&gt;                    args.run($options).await&lt;br/&gt;                },&lt;br/&gt;            )*&lt;br/&gt;        }&lt;br/&gt;    };&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:53:56&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsx4pwzcpd0zf6yzjqptsx68uet87365ajukg8u9qcl6nsaxhdkghszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs36sx88</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsx4pwzcpd0zf6yzjqptsx68uet87365ajukg8u9qcl6nsaxhdkghszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs36sx88" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::path::PathBuf;&lt;br/&gt;&lt;br/&gt;use crate::{cli::ConfigError, error::N34Result};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Default config path&lt;br/&gt;pub fn config_path() -&amp;gt; N34Result&amp;lt;PathBuf&amp;gt; {&lt;br/&gt;    Ok(dirs::config_dir()&lt;br/&gt;        .ok_or(ConfigError::CanNotFindConfigPath)?&lt;br/&gt;        .join(&amp;#34;n34&amp;#34;)&lt;br/&gt;        .join(&amp;#34;config.toml&amp;#34;))&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:53:45&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqswj8zg2uqj4dvcywufkvz2le274g2cvnu54xdhzppa4zm0rlwna5qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsu7q5h8</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqswj8zg2uqj4dvcywufkvz2le274g2cvnu54xdhzppa4zm0rlwna5qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsu7q5h8" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{collections::HashSet, fs, net::SocketAddr, path::PathBuf};&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    nips::{nip19::Nip19Coordinate, nip46::NostrConnectURI},&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::traits::{MutRepoRelaySetsExt, RepoRelaySetsExt},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Errors that can occur when working with configuration files.&lt;br/&gt;#[derive(thiserror::Error, Debug)]&lt;br/&gt;pub enum ConfigError {&lt;br/&gt;    #[error(&lt;br/&gt;        &amp;#34;Could not determine the default config path: both `$XDG_CONFIG_HOME` and `$HOME` \&lt;br/&gt;         environment variables are missing or unset.&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    CanNotFindConfigPath,&lt;br/&gt;    #[error(&amp;#34;Couldn&amp;#39;t read the config file: {0}&amp;#34;)]&lt;br/&gt;    ReadFile(std::io::Error),&lt;br/&gt;    #[error(&amp;#34;Couldn&amp;#39;t write in the config file: {0}&amp;#34;)]&lt;br/&gt;    WriteFile(std::io::Error),&lt;br/&gt;    #[error(&amp;#34;Couldn&amp;#39;t serialize the config. This is a bug, please report it: {0}&amp;#34;)]&lt;br/&gt;    Serialize(toml::ser::Error),&lt;br/&gt;    #[error(&amp;#34;Failed to parse the config file: {0}&amp;#34;)]&lt;br/&gt;    ParseFile(toml::de::Error),&lt;br/&gt;    #[error(&amp;#34;Duplicate configuration set name detected: &amp;#39;{0}&amp;#39;. Each set  must have a unique name.&amp;#34;)]&lt;br/&gt;    SetDuplicateName(String),&lt;br/&gt;    #[error(&amp;#34;No set with the given name `{0}`&amp;#34;)]&lt;br/&gt;    SetNotFound(String),&lt;br/&gt;    #[error(&amp;#34;You can&amp;#39;t create an new empty set.&amp;#34;)]&lt;br/&gt;    NewEmptySet,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Configuration for the command-line interface.&lt;br/&gt;#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]&lt;br/&gt;pub struct CliConfig {&lt;br/&gt;    /// Path to the configuration file (not serialized)&lt;br/&gt;    #[serde(skip)]&lt;br/&gt;    path:                   PathBuf,&lt;br/&gt;    /// Groups of repositories and relays.&lt;br/&gt;    #[serde(default, skip_serializing_if = &amp;#34;Vec::is_empty&amp;#34;)]&lt;br/&gt;    pub sets:               Vec&amp;lt;RepoRelaySet&amp;gt;,&lt;br/&gt;    /// The default PoW difficulty&lt;br/&gt;    #[serde(skip_serializing_if = &amp;#34;Option::is_none&amp;#34;)]&lt;br/&gt;    pub pow:                Option&amp;lt;u8&amp;gt;,&lt;br/&gt;    /// List of fallback relays used if no fallback relays was provided.&lt;br/&gt;    #[serde(skip_serializing_if = &amp;#34;Option::is_none&amp;#34;)]&lt;br/&gt;    pub fallback_relays:    Option&amp;lt;Vec&amp;lt;RelayUrl&amp;gt;&amp;gt;,&lt;br/&gt;    /// Default Nostr bunker URL used for signing events.&lt;br/&gt;    #[serde(&lt;br/&gt;        default,&lt;br/&gt;        skip_serializing_if = &amp;#34;Option::is_none&amp;#34;,&lt;br/&gt;        deserialize_with = &amp;#34;super::parsers::de_bunker_url&amp;#34;,&lt;br/&gt;        serialize_with = &amp;#34;super::parsers::ser_bunker_url&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    pub bunker_url:         Option&amp;lt;NostrConnectURI&amp;gt;,&lt;br/&gt;    /// Whether to use the system keyring to store the secret key.&lt;br/&gt;    #[serde(default)]&lt;br/&gt;    pub keyring_secret_key: bool,&lt;br/&gt;    /// Signs events using the browser&amp;#39;s NIP-07 extension.&lt;br/&gt;    #[serde(default)]&lt;br/&gt;    pub nip07:              Option&amp;lt;SocketAddr&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// A named group of repositories and relays.&lt;br/&gt;#[derive(serde::Serialize, serde::Deserialize, Default, Clone, Debug)]&lt;br/&gt;pub struct RepoRelaySet {&lt;br/&gt;    /// Unique identifier for this group.&lt;br/&gt;    pub name:   String,&lt;br/&gt;    /// Repository addresses in this group.&lt;br/&gt;    #[serde(&lt;br/&gt;        default,&lt;br/&gt;        skip_serializing_if = &amp;#34;HashSet::is_empty&amp;#34;,&lt;br/&gt;        serialize_with = &amp;#34;super::parsers::ser_naddrs&amp;#34;,&lt;br/&gt;        deserialize_with = &amp;#34;super::parsers::de_naddrs&amp;#34;&lt;br/&gt;    )]&lt;br/&gt;    pub naddrs: HashSet&amp;lt;Nip19Coordinate&amp;gt;,&lt;br/&gt;    /// Relay URLs in this group.&lt;br/&gt;    #[serde(default, skip_serializing_if = &amp;#34;HashSet::is_empty&amp;#34;)]&lt;br/&gt;    pub relays: HashSet&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CliConfig {&lt;br/&gt;    /// Reads and parse a TOML config file from the given path, creating it if&lt;br/&gt;    /// missing.&lt;br/&gt;    pub fn load(file_path: PathBuf) -&amp;gt; N34Result&amp;lt;Self&amp;gt; {&lt;br/&gt;        tracing::info!(path = %file_path.display(), &amp;#34;Loading configuration from file&amp;#34;);&lt;br/&gt;        // Make sure the file is exist&lt;br/&gt;        if let Some(parent) = file_path.parent()&lt;br/&gt;            &amp;amp;&amp;amp; !parent.exists()&lt;br/&gt;        {&lt;br/&gt;            fs::create_dir_all(parent)?;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let _ = fs::File::create_new(&amp;amp;file_path);&lt;br/&gt;&lt;br/&gt;        let mut config: Self =&lt;br/&gt;            toml::from_str(&amp;amp;fs::read_to_string(&amp;amp;file_path).map_err(ConfigError::ReadFile)?)&lt;br/&gt;                .map_err(ConfigError::ParseFile)?;&lt;br/&gt;        config.path = file_path;&lt;br/&gt;&lt;br/&gt;        config.post_sets()?;&lt;br/&gt;&lt;br/&gt;        Ok(config)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Dump the config as toml in a file&lt;br/&gt;    pub fn dump(mut self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        tracing::debug!(config = ?self, &amp;#34;Writing configuration to {}&amp;#34;, self.path.display());&lt;br/&gt;        self.post_sets()?;&lt;br/&gt;&lt;br/&gt;        fs::write(&lt;br/&gt;            &amp;amp;self.path,&lt;br/&gt;            toml::to_string_pretty(&amp;amp;self).map_err(ConfigError::Serialize)?,&lt;br/&gt;        )&lt;br/&gt;        .map_err(ConfigError::WriteFile)?;&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Performs post-processing validation on the sets after loading or before&lt;br/&gt;    /// dumping.&lt;br/&gt;    fn post_sets(&amp;amp;mut self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        self.sets.as_slice().ensure_names()?;&lt;br/&gt;        self.sets.dedup_naddrs();&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl RepoRelaySet {&lt;br/&gt;    /// Create a new [`RepoRelaySet`]&lt;br/&gt;    pub fn new(&lt;br/&gt;        name: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;        naddrs: impl IntoIterator&amp;lt;Item = Nip19Coordinate&amp;gt;,&lt;br/&gt;        relays: impl IntoIterator&amp;lt;Item = RelayUrl&amp;gt;,&lt;br/&gt;    ) -&amp;gt; Self {&lt;br/&gt;        Self {&lt;br/&gt;            name:   name.into(),&lt;br/&gt;            naddrs: HashSet::from_iter(naddrs),&lt;br/&gt;            relays: HashSet::from_iter(relays),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Removes duplicate repository addresses by comparing their coordinates,&lt;br/&gt;    /// ignoring embedded relays.&lt;br/&gt;    pub fn dedup_naddrs(&amp;amp;mut self) {&lt;br/&gt;        let mut seen = HashSet::new();&lt;br/&gt;        self.naddrs.retain(|n| seen.insert(n.coordinate.clone()));&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:53:33&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrmwl3ecedrfkkqet8muq7mv0keqlupxtyeu96847xvnvsahu8nmszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp3f5g2</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrmwl3ecedrfkkqet8muq7mv0keqlupxtyeu96847xvnvsahu8nmszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp3f5g2" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{iter, str::FromStr, sync::Arc};&lt;br/&gt;&lt;br/&gt;use either::Either;&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventBuilder, EventId, Kind, Tag, TagKind},&lt;br/&gt;    filter::Filter,&lt;br/&gt;    hashes::sha1::Hash as Sha1Hash,&lt;br/&gt;    nips::{nip10::Marker, nip19::ToBech32},&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use super::{&lt;br/&gt;    issue::IssueStatus,&lt;br/&gt;    patch::PatchStatus,&lt;br/&gt;    types::{NaddrOrSet, NostrEvent},&lt;br/&gt;};&lt;br/&gt;use crate::{&lt;br/&gt;    cli::traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;    nostr_utils::{NostrClient, traits::NaddrsUtils, utils},&lt;br/&gt;};&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, patch::GitPatch},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;    nostr_utils::traits::{GitIssueUtils, GitPatchUtils, ReposUtils},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Updates the issue&amp;#39;s status to `new_status` after validating it with&lt;br/&gt;/// `check_fn`.&lt;br/&gt;pub async fn issue_status_command(&lt;br/&gt;    options: CliOptions,&lt;br/&gt;    issue_id: NostrEvent,&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    new_status: IssueStatus,&lt;br/&gt;    check_fn: impl FnOnce(&amp;amp;IssueStatus) -&amp;gt; N34Result&amp;lt;()&amp;gt;,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;    let naddrs = utils::naddrs_or_file(&lt;br/&gt;        naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;        &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;    )?;&lt;br/&gt;    let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;    let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;    let user_pubk = client.pubkey().await?;&lt;br/&gt;    client&lt;br/&gt;        .add_relays(&amp;amp;[naddrs.extract_relays(), issue_id.relays].concat())&lt;br/&gt;        .await;&lt;br/&gt;&lt;br/&gt;    let owners = naddrs.extract_owners();&lt;br/&gt;    let coordinates = naddrs.clone().into_coordinates();&lt;br/&gt;    let repos = client.fetch_repos(&amp;amp;coordinates).await?;&lt;br/&gt;    let maintainers = repos.extract_maintainers();&lt;br/&gt;    let relay_hint = repos.extract_relays().first().cloned();&lt;br/&gt;    client.add_relays(&amp;amp;repos.extract_relays()).await;&lt;br/&gt;&lt;br/&gt;    let issue_event = client&lt;br/&gt;        .fetch_event(Filter::new().id(issue_id.event_id))&lt;br/&gt;        .await?&lt;br/&gt;        .ok_or(N34Error::CanNotFoundIssue)?;&lt;br/&gt;&lt;br/&gt;    let issue_status = client&lt;br/&gt;        .fetch_issue_status(&lt;br/&gt;            issue_id.event_id,&lt;br/&gt;            [maintainers.as_slice(), &amp;amp;[issue_event.pubkey], &amp;amp;owners].concat(),&lt;br/&gt;        )&lt;br/&gt;        .await?;&lt;br/&gt;&lt;br/&gt;    check_fn(&amp;amp;issue_status)?;&lt;br/&gt;&lt;br/&gt;    let status_event = EventBuilder::new(new_status.kind(), &amp;#34;&amp;#34;)&lt;br/&gt;        .pow(options.pow.unwrap_or_default())&lt;br/&gt;        .tag(utils::event_reply_tag(&lt;br/&gt;            &amp;amp;issue_id.event_id,&lt;br/&gt;            relay_hint.as_ref(),&lt;br/&gt;            Marker::Root,&lt;br/&gt;        ))&lt;br/&gt;        .tag(Tag::public_key(issue_event.pubkey))&lt;br/&gt;        .tags(maintainers.iter().map(|p| Tag::public_key(*p)))&lt;br/&gt;        .tags(owners.iter().map(|p| Tag::public_key(*p)))&lt;br/&gt;        .tags(&lt;br/&gt;            coordinates&lt;br/&gt;                .into_iter()&lt;br/&gt;                .map(|c| Tag::coordinate(c, relay_hint.clone())),&lt;br/&gt;        )&lt;br/&gt;        .dedup_tags()&lt;br/&gt;        .build(user_pubk);&lt;br/&gt;&lt;br/&gt;    let event_id = status_event.id.expect(&amp;#34;There is an id&amp;#34;);&lt;br/&gt;    let user_relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;    let write_relays = [&lt;br/&gt;        relays,&lt;br/&gt;        naddrs.extract_relays(),&lt;br/&gt;        repos.extract_relays(),&lt;br/&gt;        utils::add_write_relays(user_relays_list.as_ref()),&lt;br/&gt;        client.read_relays_from_user(issue_event.pubkey).await,&lt;br/&gt;        client&lt;br/&gt;            .read_relays_from_users(&amp;amp;[maintainers, owners].concat())&lt;br/&gt;            .await,&lt;br/&gt;    ]&lt;br/&gt;    .concat();&lt;br/&gt;&lt;br/&gt;    let success = client&lt;br/&gt;        .send_event_to(status_event, user_relays_list.as_ref(), &amp;amp;write_relays)&lt;br/&gt;        .await?;&lt;br/&gt;    let nevent = utils::new_nevent(event_id, &amp;amp;success)?;&lt;br/&gt;    println!(&amp;#34;Issue status created: {nevent}&amp;#34;);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Updates the patch&amp;#39;s status to `new_status` after validating it with&lt;br/&gt;/// `check_fn`.&lt;br/&gt;pub async fn patch_status_command(&lt;br/&gt;    options: CliOptions,&lt;br/&gt;    patch_id: NostrEvent,&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    new_status: PatchStatus,&lt;br/&gt;    merge_or_applied_commits: Option&amp;lt;Either&amp;lt;Sha1Hash, Vec&amp;lt;Sha1Hash&amp;gt;&amp;gt;&amp;gt;,&lt;br/&gt;    merge_or_applied_patches: Vec&amp;lt;EventId&amp;gt;,&lt;br/&gt;    check_fn: impl FnOnce(&amp;amp;PatchStatus) -&amp;gt; N34Result&amp;lt;()&amp;gt;,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;    let naddrs = utils::naddrs_or_file(&lt;br/&gt;        naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;        &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;    )?;&lt;br/&gt;    let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;    let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;    let user_pubk = client.pubkey().await?;&lt;br/&gt;    client&lt;br/&gt;        .add_relays(&amp;amp;[naddrs.extract_relays(), patch_id.relays].concat())&lt;br/&gt;        .await;&lt;br/&gt;&lt;br/&gt;    let owners = naddrs.extract_owners();&lt;br/&gt;    let coordinates = naddrs.clone().into_coordinates();&lt;br/&gt;    let repos = client.fetch_repos(&amp;amp;coordinates).await?;&lt;br/&gt;    let maintainers = repos.extract_maintainers();&lt;br/&gt;    let relay_hint = repos.extract_relays().first().cloned();&lt;br/&gt;    client.add_relays(&amp;amp;repos.extract_relays()).await;&lt;br/&gt;&lt;br/&gt;    let patch_event = client.fetch_patch(patch_id.event_id).await?;&lt;br/&gt;&lt;br/&gt;    if patch_event.is_revision_patch() &amp;amp;&amp;amp; !new_status.is_merged_or_applied() {&lt;br/&gt;        return Err(N34Error::InvalidStatus(&lt;br/&gt;            &amp;#34;Invalid action for patch revision. Only &amp;#39;apply&amp;#39; or &amp;#39;merge&amp;#39; are allowed, &amp;#39;open&amp;#39;, \&lt;br/&gt;             &amp;#39;close&amp;#39;, and &amp;#39;draft&amp;#39; are not supported.&amp;#34;&lt;br/&gt;                .to_owned(),&lt;br/&gt;        ));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let (root_patch, root_revision) = get_patch_root_revision(&amp;amp;patch_event)?;&lt;br/&gt;    let patch_status = client&lt;br/&gt;        .fetch_patch_status(&lt;br/&gt;            root_patch,&lt;br/&gt;            root_revision,&lt;br/&gt;            [maintainers.as_slice(), &amp;amp;[patch_event.pubkey], &amp;amp;owners].concat(),&lt;br/&gt;        )&lt;br/&gt;        .await?;&lt;br/&gt;&lt;br/&gt;    check_fn(&amp;amp;patch_status)?;&lt;br/&gt;&lt;br/&gt;    let mut status_builder = EventBuilder::new(new_status.kind(), &amp;#34;&amp;#34;)&lt;br/&gt;        .pow(options.pow.unwrap_or_default())&lt;br/&gt;        .tag(utils::event_reply_tag(&lt;br/&gt;            &amp;amp;root_patch,&lt;br/&gt;            relay_hint.as_ref(),&lt;br/&gt;            Marker::Root,&lt;br/&gt;        ))&lt;br/&gt;        .tag(Tag::public_key(patch_event.pubkey))&lt;br/&gt;        .tags(maintainers.iter().map(|p| Tag::public_key(*p)))&lt;br/&gt;        .tags(owners.iter().map(|p| Tag::public_key(*p)))&lt;br/&gt;        .tags(&lt;br/&gt;            coordinates&lt;br/&gt;                .into_iter()&lt;br/&gt;                .map(|c| Tag::coordinate(c, relay_hint.clone())),&lt;br/&gt;        );&lt;br/&gt;&lt;br/&gt;    if new_status.is_merged_or_applied() {&lt;br/&gt;        if let Some(merge_commit) = merge_or_applied_commits&lt;br/&gt;            .as_ref()&lt;br/&gt;            .and_then(|e| e.as_ref().left())&lt;br/&gt;        {&lt;br/&gt;            let commit = merge_commit.to_string();&lt;br/&gt;            status_builder = status_builder&lt;br/&gt;                .tag(Tag::custom(&lt;br/&gt;                    TagKind::custom(&amp;#34;merge-commit&amp;#34;),&lt;br/&gt;                    iter::once(&amp;amp;commit),&lt;br/&gt;                ))&lt;br/&gt;                .tag(Tag::reference(commit));&lt;br/&gt;        } else if let Some(applied_commits) = merge_or_applied_commits.and_then(|e| e.right()) {&lt;br/&gt;            let commits = applied_commits&lt;br/&gt;                .iter()&lt;br/&gt;                .map(ToString::to_string)&lt;br/&gt;                .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;            status_builder = status_builder&lt;br/&gt;                .tag(Tag::custom(TagKind::custom(&amp;#34;applied-as-commits&amp;#34;), &amp;amp;commits))&lt;br/&gt;                .tags(commits.into_iter().map(Tag::reference));&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        if let Some(root_revision) = root_revision {&lt;br/&gt;            status_builder = status_builder.tag(utils::event_reply_tag(&lt;br/&gt;                &amp;amp;root_revision,&lt;br/&gt;                relay_hint.as_ref(),&lt;br/&gt;                Marker::Reply,&lt;br/&gt;            ));&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        if !merge_or_applied_patches.is_empty() {&lt;br/&gt;            status_builder = status_builder.tags(&lt;br/&gt;                build_patches_quote(client.clone(), relay_hint.clone(), merge_or_applied_patches)&lt;br/&gt;                    .await,&lt;br/&gt;            );&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let status_event = status_builder.dedup_tags().build(user_pubk);&lt;br/&gt;&lt;br/&gt;    let event_id = status_event.id.expect(&amp;#34;There is an id&amp;#34;);&lt;br/&gt;    let user_relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;    let write_relays = [&lt;br/&gt;        relays,&lt;br/&gt;        naddrs.extract_relays(),&lt;br/&gt;        repos.extract_relays(),&lt;br/&gt;        utils::add_write_relays(user_relays_list.as_ref()),&lt;br/&gt;        client.read_relays_from_user(patch_event.pubkey).await,&lt;br/&gt;        client&lt;br/&gt;            .read_relays_from_users(&amp;amp;[maintainers, owners].concat())&lt;br/&gt;            .await,&lt;br/&gt;    ]&lt;br/&gt;    .concat();&lt;br/&gt;&lt;br/&gt;    let success = client&lt;br/&gt;        .send_event_to(status_event, user_relays_list.as_ref(), &amp;amp;write_relays)&lt;br/&gt;        .await?;&lt;br/&gt;    let nevent = utils::new_nevent(event_id, &amp;amp;success)?;&lt;br/&gt;    println!(&amp;#34;Patch status created: {nevent}&amp;#34;);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Fetch and display patches and issues for given repositories.&lt;br/&gt;/// If `list_patches` is true, lists patches instead of issues.&lt;br/&gt;/// `limit` controls the maximum number of items to fetch.&lt;br/&gt;pub async fn list_patches_and_issues(&lt;br/&gt;    options: CliOptions,&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    list_patches: bool,&lt;br/&gt;    limit: usize,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;    let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(&lt;br/&gt;        naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;        &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;    )?)?;&lt;br/&gt;&lt;br/&gt;    let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;    let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;    client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;&lt;br/&gt;    let coordinates = naddrs.clone().into_coordinates();&lt;br/&gt;    let repos = client.fetch_repos(&amp;amp;coordinates).await?;&lt;br/&gt;    let authorized_pubkeys = [naddrs.extract_owners(), repos.extract_maintainers()].concat();&lt;br/&gt;    client.add_relays(&amp;amp;repos.extract_relays()).await;&lt;br/&gt;    // This helps discover issues and their status.&lt;br/&gt;    client&lt;br/&gt;        .add_relays(&amp;amp;client.read_relays_from_users(&amp;amp;authorized_pubkeys).await)&lt;br/&gt;        .await;&lt;br/&gt;&lt;br/&gt;    let kind = if list_patches {&lt;br/&gt;        Kind::GitPatch&lt;br/&gt;    } else {&lt;br/&gt;        Kind::GitIssue&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    let mut filter = Filter::new()&lt;br/&gt;        .coordinates(coordinates.iter())&lt;br/&gt;        .kind(kind)&lt;br/&gt;        .limit(limit);&lt;br/&gt;&lt;br/&gt;    if list_patches {&lt;br/&gt;        filter = filter.hashtag(&amp;#34;root&amp;#34;);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let arc_client = Arc::new(client);&lt;br/&gt;    // Events are sorted by kind in ascending order:&lt;br/&gt;    // 1630 (Open), 1631 (Resolved/Applied), 1632 (Closed), 1633 (Draft)&lt;br/&gt;    let events = utils::sort_by_key(&lt;br/&gt;        future::join_all(&lt;br/&gt;            arc_client&lt;br/&gt;                .fetch_events(filter)&lt;br/&gt;                .await?&lt;br/&gt;                .into_iter()&lt;br/&gt;                .take(limit)&lt;br/&gt;                .map(|event| {&lt;br/&gt;                    let c = arc_client.clone();&lt;br/&gt;                    let keys = authorized_pubkeys.clone();&lt;br/&gt;                    async move {&lt;br/&gt;                        let status = if list_patches {&lt;br/&gt;                            let (root, root_revision) = get_patch_root_revision(&amp;amp;event)?;&lt;br/&gt;                            c.fetch_patch_status(&lt;br/&gt;                                root,&lt;br/&gt;                                root_revision,&lt;br/&gt;                                [keys.as_slice(), &amp;amp;[event.pubkey]].concat(),&lt;br/&gt;                            )&lt;br/&gt;                            .await&lt;br/&gt;                            .map(Either::Left)?&lt;br/&gt;                        } else {&lt;br/&gt;                            c.fetch_issue_status(&lt;br/&gt;                                event.id,&lt;br/&gt;                                [keys.as_slice(), &amp;amp;[event.pubkey]].concat(),&lt;br/&gt;                            )&lt;br/&gt;                            .await&lt;br/&gt;                            .map(Either::Right)?&lt;br/&gt;                        };&lt;br/&gt;                        N34Result::Ok((event, status))&lt;br/&gt;                    }&lt;br/&gt;                }),&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;        .into_iter()&lt;br/&gt;        .filter_map(|r| r.ok()),&lt;br/&gt;        |(_, status)| status.as_ref().either_into::&amp;lt;Kind&amp;gt;(),&lt;br/&gt;    );&lt;br/&gt;&lt;br/&gt;    let lines = events&lt;br/&gt;        .map(|(event, status)| format_patch_and_issue(&amp;amp;event, status))&lt;br/&gt;        .collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;    let max_width = lines&lt;br/&gt;        .iter()&lt;br/&gt;        .map(|s| s.split_once(&amp;#39;\n&amp;#39;).map_or(85, |(l, _)| l.chars().count()))&lt;br/&gt;        .max()&lt;br/&gt;        .unwrap_or(85)&lt;br/&gt;        .max(67); // length of the event id&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;{}&amp;#34;, lines.join(&amp;amp;format!(&amp;#34;{}\n&amp;#34;, &amp;#34;-&amp;#34;.repeat(max_width))));&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns a tuple of (root_id, patch_id) if this is a valid root or revision&lt;br/&gt;/// patch.&lt;br/&gt;fn get_patch_root_revision(patch_event: &amp;amp;Event) -&amp;gt; N34Result&amp;lt;(EventId, Option&amp;lt;EventId&amp;gt;)&amp;gt; {&lt;br/&gt;    if patch_event.is_revision_patch() {&lt;br/&gt;        Ok((&lt;br/&gt;            patch_event.root_patch_from_revision()?,&lt;br/&gt;            Some(patch_event.id),&lt;br/&gt;        ))&lt;br/&gt;    } else if patch_event.is_root_patch() {&lt;br/&gt;        Ok((patch_event.id, None))&lt;br/&gt;    } else {&lt;br/&gt;        Err(N34Error::NotRootPatch)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Formats an event as either a patch or an issue. For patches, extracts the&lt;br/&gt;/// subject line from the Git patch format. For issues, combines the subject&lt;br/&gt;/// with labels. The output includes status and formatted ID.&lt;br/&gt;fn format_patch_and_issue(event: &amp;amp;Event, status: Either&amp;lt;PatchStatus, IssueStatus&amp;gt;) -&amp;gt; String {&lt;br/&gt;    let subject = if status.is_left() {&lt;br/&gt;        GitPatch::from_str(&amp;amp;event.content)&lt;br/&gt;            .map(|p| p.subject)&lt;br/&gt;            .unwrap_or_else(|_| {&lt;br/&gt;                event&lt;br/&gt;                    .content&lt;br/&gt;                    .lines()&lt;br/&gt;                    .find(|line| line.trim().starts_with(&amp;#34;Subject: &amp;#34;))&lt;br/&gt;                    .unwrap_or_default()&lt;br/&gt;                    .trim()&lt;br/&gt;                    .trim_start_matches(&amp;#34;Subject: &amp;#34;)&lt;br/&gt;                    .to_owned()&lt;br/&gt;            })&lt;br/&gt;    } else {&lt;br/&gt;        let labels = event.extract_issue_labels();&lt;br/&gt;        let subject = event.extract_issue_subject();&lt;br/&gt;&lt;br/&gt;        if labels.is_empty() {&lt;br/&gt;            subject.to_owned()&lt;br/&gt;        } else {&lt;br/&gt;            format!(r#&amp;#34;&amp;#34;{subject}&amp;#34; {labels}&amp;#34;#)&lt;br/&gt;        }&lt;br/&gt;    };&lt;br/&gt;    format!(&lt;br/&gt;        &amp;#34;({status}) {}\nID: {}\n&amp;#34;,&lt;br/&gt;        utils::smart_wrap(&amp;amp;subject, 85),&lt;br/&gt;        event.id.to_bech32().expect(&amp;#34;Infallible&amp;#34;)&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Generates a list of tags for quoting patches in merge/applied status events.&lt;br/&gt;async fn build_patches_quote(&lt;br/&gt;    client: NostrClient,&lt;br/&gt;    relay_hint: Option&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;    patches: Vec&amp;lt;EventId&amp;gt;,&lt;br/&gt;) -&amp;gt; Vec&amp;lt;Tag&amp;gt; {&lt;br/&gt;    let client = Arc::new(client);&lt;br/&gt;    let relay_hint = Arc::new(relay_hint);&lt;br/&gt;&lt;br/&gt;    future::join_all(patches.into_iter().map(|eid| {&lt;br/&gt;        let task_relay = Arc::clone(&amp;amp;relay_hint);&lt;br/&gt;        let task_client = Arc::clone(&amp;amp;client);&lt;br/&gt;&lt;br/&gt;        async move {&lt;br/&gt;            Tag::custom(&lt;br/&gt;                TagKind::q(),&lt;br/&gt;                [&lt;br/&gt;                    eid.to_hex(),&lt;br/&gt;                    task_relay&lt;br/&gt;                        .as_ref()&lt;br/&gt;                        .as_ref()&lt;br/&gt;                        .map(|r| r.to_string())&lt;br/&gt;                        .unwrap_or_default(),&lt;br/&gt;                    task_client&lt;br/&gt;                        .event_author(eid)&lt;br/&gt;                        .await&lt;br/&gt;                        .ok()&lt;br/&gt;                        .flatten()&lt;br/&gt;                        .map(|p| p.to_hex())&lt;br/&gt;                        .unwrap_or_default(),&lt;br/&gt;                ],&lt;br/&gt;            )&lt;br/&gt;        }&lt;br/&gt;    }))&lt;br/&gt;    .await&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:53:20&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqszkqmj5lsnq8wdf3p85uylc487dxshhz6krlud79ens7gc4qyjjlszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsar55mf</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqszkqmj5lsnq8wdf3p85uylc487dxshhz6krlud79ens7gc4qyjjlszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsar55mf" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::collections::HashSet;&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, RelayOrSet},&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct UpdateArgs {&lt;br/&gt;    /// Name of the set to update&lt;br/&gt;    name:         String,&lt;br/&gt;    /// Add relay to the set, either as URL or set name to extract its relays.&lt;br/&gt;    /// [aliases: `--sr`]&lt;br/&gt;    #[arg(long = &amp;#34;set-relay&amp;#34;, alias(&amp;#34;sr&amp;#34;))]&lt;br/&gt;    relays:       Vec&amp;lt;RelayOrSet&amp;gt;,&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:       Vec&amp;lt;NaddrOrSet&amp;gt;,&lt;br/&gt;    /// Replace existing relays/repositories instead of adding to them&lt;br/&gt;    #[arg(long = &amp;#34;override&amp;#34;)]&lt;br/&gt;    override_set: bool,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for UpdateArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?;&lt;br/&gt;        let relays = self.relays.flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;&lt;br/&gt;        let set = options.config.sets.get_mut_set(&amp;amp;self.name)?;&lt;br/&gt;&lt;br/&gt;        if self.override_set {&lt;br/&gt;            if !relays.is_empty() {&lt;br/&gt;                set.relays = HashSet::from_iter(relays);&lt;br/&gt;            }&lt;br/&gt;            if !naddrs.is_empty() {&lt;br/&gt;                set.naddrs = HashSet::from_iter(naddrs)&lt;br/&gt;            }&lt;br/&gt;        } else {&lt;br/&gt;            set.relays.extend(relays);&lt;br/&gt;            set.naddrs.extend(naddrs);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:53:09&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2pyt6nl7smgkp6agxk2ctj0dg0werun5hevg6jgnyqh6r0esgnaqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsyn24vw</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2pyt6nl7smgkp6agxk2ctj0dg0werun5hevg6jgnyqh6r0esgnaqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsyn24vw" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::nips::nip19::ToBech32;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        RepoRelaySet,&lt;br/&gt;        traits::{CommandRunner, RepoRelaySetsExt},&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct ShowArgs {&lt;br/&gt;    /// Name of the set to display. If not provided, lists all available sets.&lt;br/&gt;    name: Option&amp;lt;String&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ShowArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if let Some(name) = self.name {&lt;br/&gt;            println!(&lt;br/&gt;                &amp;#34;{}&amp;#34;,&lt;br/&gt;                format_set(options.config.sets.as_slice().get_set(&amp;amp;name)?)&lt;br/&gt;            );&lt;br/&gt;        } else {&lt;br/&gt;            println!(&lt;br/&gt;                &amp;#34;{}&amp;#34;,&lt;br/&gt;                options&lt;br/&gt;                    .config&lt;br/&gt;                    .sets&lt;br/&gt;                    .iter()&lt;br/&gt;                    .map(format_set)&lt;br/&gt;                    .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;                    .join(&amp;#34;\n----------\n&amp;#34;)&lt;br/&gt;            );&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Format a set to view it to the user&lt;br/&gt;fn format_set(set: &amp;amp;RepoRelaySet) -&amp;gt; String {&lt;br/&gt;    let naddrs = if set.naddrs.is_empty() {&lt;br/&gt;        &amp;#34;Nothing&amp;#34;.to_owned()&lt;br/&gt;    } else {&lt;br/&gt;        format!(&lt;br/&gt;            &amp;#34;\n- {}&amp;#34;,&lt;br/&gt;            set.naddrs&lt;br/&gt;                .iter()&lt;br/&gt;                .map(|naddr| naddr.to_bech32().expect(&amp;#34;We did decoded before&amp;#34;))&lt;br/&gt;                .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;                .join(&amp;#34;\n- &amp;#34;)&lt;br/&gt;        )&lt;br/&gt;    };&lt;br/&gt;    let relays = if set.relays.is_empty() {&lt;br/&gt;        &amp;#34;Nothing&amp;#34;.to_owned()&lt;br/&gt;    } else {&lt;br/&gt;        format!(&lt;br/&gt;            &amp;#34;\n- {}&amp;#34;,&lt;br/&gt;            set.relays&lt;br/&gt;                .iter()&lt;br/&gt;                .map(|relay| relay.to_string())&lt;br/&gt;                .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;                .join(&amp;#34;\n- &amp;#34;)&lt;br/&gt;        )&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    format!(&lt;br/&gt;        &amp;#34;Name: {}\nّّRepositories: {naddrs}\nRelays: {relays}&amp;#34;,&lt;br/&gt;        set.name&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:58&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsr8998k43hmsuz8km7x32p7l5swdthtr0xlapr857kfht5gt2uxjqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsflr8ta</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsr8998k43hmsuz8km7x32p7l5swdthtr0xlapr857kfht5gt2uxjqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsflr8ta" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, RelayOrSet},&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct RemoveArgs {&lt;br/&gt;    /// Set name to delete&lt;br/&gt;    name:   String,&lt;br/&gt;    /// Specific relay to remove it from the set, either as URL or set name to&lt;br/&gt;    /// extract its relays. [aliases: `--sr`]&lt;br/&gt;    #[arg(long = &amp;#34;set-relay&amp;#34;, alias(&amp;#34;sr&amp;#34;))]&lt;br/&gt;    relays: Vec&amp;lt;RelayOrSet&amp;gt;,&lt;br/&gt;&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs: Vec&amp;lt;NaddrOrSet&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for RemoveArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?;&lt;br/&gt;        let relays = self.relays.flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;&lt;br/&gt;        if relays.is_empty() &amp;amp;&amp;amp; naddrs.is_empty() {&lt;br/&gt;            options.config.sets.remove_set(self.name)?;&lt;br/&gt;        } else {&lt;br/&gt;            if !relays.is_empty() {&lt;br/&gt;                options&lt;br/&gt;                    .config&lt;br/&gt;                    .sets&lt;br/&gt;                    .remove_relays(&amp;amp;self.name, relays.into_iter())?;&lt;br/&gt;            }&lt;br/&gt;            if !naddrs.is_empty() {&lt;br/&gt;                options&lt;br/&gt;                    .config&lt;br/&gt;                    .sets&lt;br/&gt;                    .remove_naddrs(self.name, naddrs.into_iter())?;&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:46&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxyn49y8j6q65qtfngdhr2m8p2st9l2a3mq2v0zxa489jkd4zglvqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4j3lz6</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxyn49y8j6q65qtfngdhr2m8p2st9l2a3mq2v0zxa489jkd4zglvqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4j3lz6" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        ConfigError,&lt;br/&gt;        traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, RelayOrSet},&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct NewArgs {&lt;br/&gt;    /// Unique name for the set&lt;br/&gt;    name:   String,&lt;br/&gt;    /// Optional relay to add it to the set, either as URL or set name to&lt;br/&gt;    /// extract its relays. [aliases: `--sr`]&lt;br/&gt;    #[arg(long = &amp;#34;set-relay&amp;#34;, alias(&amp;#34;sr&amp;#34;))]&lt;br/&gt;    relays: Vec&amp;lt;RelayOrSet&amp;gt;,&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs: Vec&amp;lt;NaddrOrSet&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for NewArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?;&lt;br/&gt;        let relays = self.relays.flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;&lt;br/&gt;        if relays.is_empty() &amp;amp;&amp;amp; naddrs.is_empty() {&lt;br/&gt;            return Err(ConfigError::NewEmptySet.into());&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.sets.push_set(self.name, naddrs, relays)?;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:35&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqszaw2ma2qr5q8d2q3652xkeltnr8yut8q50rad2qk5h3a2mg6hesczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsekkahp</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqszaw2ma2qr5q8d2q3652xkeltnr8yut8q50rad2qk5h3a2mg6hesczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsekkahp" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `sets new` command&lt;br/&gt;mod new;&lt;br/&gt;/// `sets remove` command&lt;br/&gt;mod remove;&lt;br/&gt;/// `sets show` commands&lt;br/&gt;mod show;&lt;br/&gt;/// `sets update` command&lt;br/&gt;mod update;&lt;br/&gt;&lt;br/&gt;use clap::Subcommand;&lt;br/&gt;&lt;br/&gt;use self::new::NewArgs;&lt;br/&gt;use self::remove::RemoveArgs;&lt;br/&gt;use self::show::ShowArgs;&lt;br/&gt;use self::update::UpdateArgs;&lt;br/&gt;use super::{CliOptions, CommandRunner};&lt;br/&gt;use crate::error::N34Result;&lt;br/&gt;&lt;br/&gt;#[derive(Subcommand, Debug)]&lt;br/&gt;pub enum SetsSubcommands {&lt;br/&gt;    /// Remove a set, or specific repos and relays within it&lt;br/&gt;    Remove(RemoveArgs),&lt;br/&gt;    /// Create a new set&lt;br/&gt;    New(NewArgs),&lt;br/&gt;    /// Modify an existing set&lt;br/&gt;    Update(UpdateArgs),&lt;br/&gt;    /// Show a single set or all the stored sets&lt;br/&gt;    Show(ShowArgs),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl CommandRunner for SetsSubcommands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::run_command!(self, options, &amp;amp; Remove New Update Show)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:25&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxtges8yu8qpqtnfsl594mt7xfvdq7skq40d6gn323z073sh97zmgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs78dp64</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxtges8yu8qpqtnfsl594mt7xfvdq7skq40d6gn323z073sh97zmgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs78dp64" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::fmt;&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::nips::nip19::ToBech32;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        CommandRunner,&lt;br/&gt;        traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::NaddrOrSet,&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;    nostr_utils::{NostrClient, traits::NaddrsUtils, utils},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Arguments for the `repo view` command&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct ViewArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;)]&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ViewArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(&lt;br/&gt;            self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;            &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;        )?)?;&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;&lt;br/&gt;        let repos = client.fetch_repos(&amp;amp;naddrs.into_coordinates()).await?;&lt;br/&gt;        let mut repos_details: Vec&amp;lt;String&amp;gt; = Vec::new();&lt;br/&gt;&lt;br/&gt;        for repo in repos {&lt;br/&gt;            let mut repo_details = format!(&amp;#34;ID: {}&amp;#34;, repo.id);&lt;br/&gt;&lt;br/&gt;            if let Some(name) = repo.name {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nName: {name}&amp;#34;));&lt;br/&gt;            }&lt;br/&gt;            if let Some(desc) = repo.description {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nDescription: {desc}&amp;#34;));&lt;br/&gt;            }&lt;br/&gt;            if !repo.web.is_empty() {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nWebpages:\n{}&amp;#34;, format_list(repo.web)));&lt;br/&gt;            }&lt;br/&gt;            if !repo.clone.is_empty() {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nClone urls:\n{}&amp;#34;, format_list(repo.clone)));&lt;br/&gt;            }&lt;br/&gt;            if !repo.relays.is_empty() {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nRelays:\n{}&amp;#34;, format_list(repo.relays)));&lt;br/&gt;            }&lt;br/&gt;            if let Some(euc) = repo.euc {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&amp;#34;\nEarliest unique commit: {euc}&amp;#34;));&lt;br/&gt;            }&lt;br/&gt;            if !repo.maintainers.is_empty() {&lt;br/&gt;                repo_details.push_str(&amp;amp;format!(&lt;br/&gt;                    &amp;#34;\nMaintainers:\n{}&amp;#34;,&lt;br/&gt;                    format_list(&lt;br/&gt;                        repo.maintainers&lt;br/&gt;                            .iter()&lt;br/&gt;                            .map(|p| p.to_bech32().expect(&amp;#34;Infallible&amp;#34;))&lt;br/&gt;                    )&lt;br/&gt;                ));&lt;br/&gt;            }&lt;br/&gt;            repos_details.push(repo_details);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        println!(&amp;#34;{}&amp;#34;, repos_details.join(&amp;#34;\n----------\n&amp;#34;));&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Format a vector to print it&lt;br/&gt;fn format_list&amp;lt;I, T&amp;gt;(iterator: I) -&amp;gt; String&lt;br/&gt;where&lt;br/&gt;    I: IntoIterator&amp;lt;Item = T&amp;gt;,&lt;br/&gt;    T: fmt::Display,&lt;br/&gt;{&lt;br/&gt;    iterator&lt;br/&gt;        .into_iter()&lt;br/&gt;        .map(|t| format!(&amp;#34; - {t}&amp;#34;))&lt;br/&gt;        .collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()&lt;br/&gt;        .join(&amp;#34;\n&amp;#34;)&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:14&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs0rm70lkellg5zf4pu7zc3apsdxlkxe9am453x2ehw0n8qgt28lfgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsdyn0c0</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs0rm70lkellg5zf4pu7zc3apsdxlkxe9am453x2ehw0n8qgt28lfgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsdyn0c0" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `repo announce` subcommand&lt;br/&gt;mod announce;&lt;br/&gt;/// `repo view` subcommand&lt;br/&gt;mod view;&lt;br/&gt;&lt;br/&gt;use clap::Subcommand;&lt;br/&gt;&lt;br/&gt;use self::announce::AnnounceArgs;&lt;br/&gt;use self::view::ViewArgs;&lt;br/&gt;use super::{CliOptions, CommandRunner};&lt;br/&gt;use crate::error::N34Result;&lt;br/&gt;&lt;br/&gt;#[derive(Subcommand, Debug)]&lt;br/&gt;pub enum RepoSubcommands {&lt;br/&gt;    /// View details of a nostr git repository&lt;br/&gt;    View(ViewArgs),&lt;br/&gt;    /// Broadcast and update a git repository&lt;br/&gt;    Announce(AnnounceArgs),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for RepoSubcommands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::run_command!(self, options, &amp;amp; View Announce)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:52:03&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsz3f9uyljxwr2n32fguspdfpzmwkzdrfrvw8kn0kljgflz8pyxupqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxs57pn</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsz3f9uyljxwr2n32fguspdfpzmwkzdrfrvw8kn0kljgflz8pyxupqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxs57pn" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{fs, io::Write};&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{event::EventBuilder, key::PublicKey, types::Url};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, CommandRunner, NOSTR_ADDRESS_FILE, traits::RelayOrSetVecExt},&lt;br/&gt;    error::N34Result,&lt;br/&gt;    nostr_utils::{NostrClient, traits::NewGitRepositoryAnnouncement, utils},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Header written to new `nostr-address` files. Contains two trailing newline&lt;br/&gt;/// for formatting.&lt;br/&gt;const NOSTR_ADDRESS_FILE_HEADER: &amp;amp;str = r##&amp;#34;# This file contains NIP-19 `naddr` entities for repositories that accept this&lt;br/&gt;# project&amp;#39;s issues and patches.&lt;br/&gt;#&lt;br/&gt;# The file acts as a **read-only reference** for retrieving repository relays&lt;br/&gt;# when embedded in an `naddr` and mentions those repositories when opening&lt;br/&gt;# patches or issues. Modifications here will not affect in the relays, as the&lt;br/&gt;# file is **explicitly untracked**. Its goal is to simplify contributions by&lt;br/&gt;# removing the need for manual address entry.&lt;br/&gt;#&lt;br/&gt;# Each entry must start with &amp;#34;naddr&amp;#34;. Embedded relays are **strongly recommended**&lt;br/&gt;# to assist client-side discovery.&lt;br/&gt;#&lt;br/&gt;# Empty lines are ignored. Lines starting with &amp;#34;#&amp;#34; are treated as comments.&lt;br/&gt;&lt;br/&gt;&amp;#34;##;&lt;br/&gt;&lt;br/&gt;/// Arguments for the `repo announce` command&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct AnnounceArgs {&lt;br/&gt;    /// Unique identifier for the repository in kebab-case.&lt;br/&gt;    #[arg(long = &amp;#34;id&amp;#34;)]&lt;br/&gt;    repo_id:      String,&lt;br/&gt;    /// A name for the repository.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    name:         Option&amp;lt;String&amp;gt;,&lt;br/&gt;    /// A description for the repository.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    description:  Option&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Webpage URLs for the repository (if provided by the git server).&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    web:          Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;    /// URLs for cloning the repository.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    clone:        Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;    /// Additional maintainers of the repository (besides yourself).&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    maintainers:  Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    /// Labels to categorize the repository. Can be specified multiple times.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    label:        Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Skip kebab-case validation for the repository ID&lt;br/&gt;    #[arg(long)]&lt;br/&gt;    force_id:     bool,&lt;br/&gt;    /// If set, creates a `nostr-address` file to enable automatic address&lt;br/&gt;    /// discovery by n34&lt;br/&gt;    #[arg(long)]&lt;br/&gt;    address_file: bool,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for AnnounceArgs {&lt;br/&gt;    const NEED_RELAYS: bool = true;&lt;br/&gt;&lt;br/&gt;    async fn run(mut self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        let user_pubk = client.pubkey().await?;&lt;br/&gt;        let relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;        client&lt;br/&gt;            .add_relays(&amp;amp;utils::add_read_relays(relays_list.as_ref()))&lt;br/&gt;            .await;&lt;br/&gt;&lt;br/&gt;        if !self.maintainers.contains(&amp;amp;user_pubk) {&lt;br/&gt;            self.maintainers.insert(0, user_pubk);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let naddr = utils::repo_naddr(&amp;amp;self.repo_id, user_pubk, &amp;amp;relays)?;&lt;br/&gt;        let event = EventBuilder::new_git_repo(&lt;br/&gt;            self.repo_id,&lt;br/&gt;            self.name.map(utils::str_trim),&lt;br/&gt;            self.description.map(utils::str_trim),&lt;br/&gt;            self.web,&lt;br/&gt;            self.clone,&lt;br/&gt;            relays.clone(),&lt;br/&gt;            self.maintainers.clone(),&lt;br/&gt;            self.label.into_iter().map(utils::str_trim).collect(),&lt;br/&gt;            self.force_id,&lt;br/&gt;        )?&lt;br/&gt;        .dedup_tags()&lt;br/&gt;        .pow(options.pow.unwrap_or_default())&lt;br/&gt;        .build(user_pubk);&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;        if self.address_file {&lt;br/&gt;            let address_path = std::env::current_dir()?.join(NOSTR_ADDRESS_FILE);&lt;br/&gt;            if !address_path.exists() {&lt;br/&gt;                tracing::info!(&lt;br/&gt;                    &amp;#34;Creating new address file: &amp;#39;{NOSTR_ADDRESS_FILE}&amp;#39; at path &amp;#39;{}&amp;#39; with default \&lt;br/&gt;                     header&amp;#34;,&lt;br/&gt;                    address_path.display()&lt;br/&gt;                );&lt;br/&gt;                fs::write(&amp;amp;address_path, NOSTR_ADDRESS_FILE_HEADER)?;&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            let mut file = fs::OpenOptions::new().append(true).open(&amp;amp;address_path)?;&lt;br/&gt;&lt;br/&gt;            tracing::info!(&amp;#34;Appending naddr &amp;#39;{naddr}&amp;#39; to address file: &amp;#39;{NOSTR_ADDRESS_FILE}&amp;#39;&amp;#34;);&lt;br/&gt;            file.write_all(format!(&amp;#34;{naddr}\n&amp;#34;).as_bytes())?;&lt;br/&gt;            tracing::info!(&amp;#34;Successfully wrote naddr to address file&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let write_relays = [&lt;br/&gt;            relays,&lt;br/&gt;            utils::add_write_relays(relays_list.as_ref()),&lt;br/&gt;            // Include read relays for each maintainer (if found)&lt;br/&gt;            future::join_all(&lt;br/&gt;                self.maintainers&lt;br/&gt;                    .iter()&lt;br/&gt;                    .map(|pkey| client.read_relays_from_user(*pkey)),&lt;br/&gt;            )&lt;br/&gt;            .await&lt;br/&gt;            .into_iter()&lt;br/&gt;            .flatten()&lt;br/&gt;            .collect(),&lt;br/&gt;        ]&lt;br/&gt;        .concat();&lt;br/&gt;        let nevent = utils::new_nevent(event.id.expect(&amp;#34;There is an id&amp;#34;), &amp;amp;write_relays)?;&lt;br/&gt;&lt;br/&gt;        client&lt;br/&gt;            .send_event_to(event, relays_list.as_ref(), &amp;amp;write_relays)&lt;br/&gt;            .await?;&lt;br/&gt;&lt;br/&gt;        println!(&amp;#34;Event: {nevent}&amp;#34;,);&lt;br/&gt;        println!(&amp;#34;Repo Address: {naddr}&amp;#34;,);&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:51:52&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqswlrlmhrpyekzwgu6zxccxehj6438tx9n85xjhgn73t4k7uxkjnnszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6k5f2q</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqswlrlmhrpyekzwgu6zxccxehj6438tx9n85xjhgn73t4k7uxkjnnszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6k5f2q" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::fs;&lt;br/&gt;&lt;br/&gt;use clap::{ArgGroup, Args};&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventBuilder, Kind},&lt;br/&gt;    filter::Filter,&lt;br/&gt;    nips::nip01::Coordinate,&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use super::{CliOptions, CommandRunner};&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;    nostr_utils::{&lt;br/&gt;        NostrClient,&lt;br/&gt;        traits::{NaddrsUtils, ReposUtils},&lt;br/&gt;        utils,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// The max date &amp;#34;9999-01-01 at 00:00 UTC&amp;#34;&lt;br/&gt;const MAX_DATE: i64 = 253370764800;&lt;br/&gt;&lt;br/&gt;/// Arguments for the `reply` command&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;#[clap(&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;comment-content&amp;#34;)&lt;br/&gt;            .args([&amp;#34;comment&amp;#34;, &amp;#34;editor&amp;#34;])&lt;br/&gt;            .required(true)&lt;br/&gt;    ),&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;quote-reply-to&amp;#34;)&lt;br/&gt;            .args([&amp;#34;comment&amp;#34;, &amp;#34;quote_to&amp;#34;])&lt;br/&gt;    )&lt;br/&gt;)]&lt;br/&gt;pub struct ReplyArgs {&lt;br/&gt;    /// The issue, patch, or comment to reply to&lt;br/&gt;    #[arg(value_name = &amp;#34;nevent1-or-note1&amp;#34;)]&lt;br/&gt;    to:       NostrEvent,&lt;br/&gt;    /// Quote the replied-to event in the editor&lt;br/&gt;    #[arg(long)]&lt;br/&gt;    quote_to: bool,&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The comment (cannot be used with --editor)&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    comment:  Option&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Open editor to write comment (cannot be used with --content)&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    editor:   bool,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ReplyArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let nostr_address_path = utils::nostr_address_path()?;&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        let user_pubk = client.pubkey().await?;&lt;br/&gt;        let repo_naddrs = if let Some(naddrs) = self.naddrs.flat_naddrs(&amp;amp;options.config.sets)? {&lt;br/&gt;            client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;            Some(naddrs)&lt;br/&gt;        } else if fs::exists(&amp;amp;nostr_address_path).is_ok() {&lt;br/&gt;            let naddrs = utils::naddrs_or_file(None, &amp;amp;nostr_address_path)?;&lt;br/&gt;            client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;            Some(naddrs)&lt;br/&gt;        } else {&lt;br/&gt;            None&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        client.add_relays(&amp;amp;self.to.relays).await;&lt;br/&gt;        let relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;        let author_read_relays =&lt;br/&gt;            utils::add_read_relays(client.user_relays_list(user_pubk).await?.as_ref());&lt;br/&gt;        client.add_relays(&amp;amp;author_read_relays).await;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;        let reply_to = client&lt;br/&gt;            .fetch_event(Filter::new().id(self.to.event_id))&lt;br/&gt;            .await?&lt;br/&gt;            .ok_or(N34Error::EventNotFound)?;&lt;br/&gt;        let root = client.find_root(reply_to.clone()).await?;&lt;br/&gt;&lt;br/&gt;        let repos_coordinate = if let Some(naddrs) = repo_naddrs {&lt;br/&gt;            naddrs.into_coordinates()&lt;br/&gt;        } else if let Some(ref root_event) = root {&lt;br/&gt;            coordinates_from_root(root_event)?&lt;br/&gt;        } else {&lt;br/&gt;            return Err(N34Error::NotFoundRepo);&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        let repos = client.fetch_repos(&amp;amp;repos_coordinate).await?;&lt;br/&gt;        let maintainers = repos.extract_maintainers();&lt;br/&gt;&lt;br/&gt;        let quoted_content = if self.quote_to {&lt;br/&gt;            Some(quote_reply_to_content(&amp;amp;client, &amp;amp;reply_to).await)&lt;br/&gt;        } else {&lt;br/&gt;            None&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        let content = utils::get_content(self.comment.as_ref(), quoted_content.as_ref(), &amp;#34;.txt&amp;#34;)?;&lt;br/&gt;        let content_details = client.parse_content(&amp;amp;content).await;&lt;br/&gt;&lt;br/&gt;        let event = EventBuilder::comment(&lt;br/&gt;            content,&lt;br/&gt;            &amp;amp;reply_to,&lt;br/&gt;            root.as_ref(),&lt;br/&gt;            repos.first().and_then(|r| r.relays.first()).cloned(),&lt;br/&gt;        )&lt;br/&gt;        .dedup_tags()&lt;br/&gt;        .pow(options.pow.unwrap_or_default())&lt;br/&gt;        .tags(content_details.clone().into_tags())&lt;br/&gt;        .build(user_pubk);&lt;br/&gt;&lt;br/&gt;        let event_id = event.id.expect(&amp;#34;There is an id&amp;#34;);&lt;br/&gt;        let write_relays = [&lt;br/&gt;            relays,&lt;br/&gt;            utils::add_write_relays(relays_list.as_ref()),&lt;br/&gt;            // Merge repository announcement relays into write relays&lt;br/&gt;            repos.extract_relays(),&lt;br/&gt;            // Include read relays for each repository maintainer (if found)&lt;br/&gt;            client.read_relays_from_users(&amp;amp;maintainers).await,&lt;br/&gt;            // read relays of the root event and the reply to event&lt;br/&gt;            {&lt;br/&gt;                let (r1, r2) = future::join(&lt;br/&gt;                    client.read_relays_from_user(reply_to.pubkey),&lt;br/&gt;                    event_author_read_relays(&amp;amp;client, root.as_ref()),&lt;br/&gt;                )&lt;br/&gt;                .await;&lt;br/&gt;                [r1, r2].concat()&lt;br/&gt;            },&lt;br/&gt;            content_details.write_relays.into_iter().collect(),&lt;br/&gt;        ]&lt;br/&gt;        .concat();&lt;br/&gt;&lt;br/&gt;        tracing::trace!(relays = ?write_relays, &amp;#34;Write relays list&amp;#34;);&lt;br/&gt;        let (success, ..) = futures::join!(&lt;br/&gt;            client.send_event_to(event, relays_list.as_ref(), &amp;amp;write_relays),&lt;br/&gt;            client.broadcast(&amp;amp;reply_to, &amp;amp;author_read_relays),&lt;br/&gt;            async {&lt;br/&gt;                if let Some(root_event) = root {&lt;br/&gt;                    let _ = client.broadcast(&amp;amp;root_event, &amp;amp;author_read_relays).await;&lt;br/&gt;                }&lt;br/&gt;            },&lt;br/&gt;        );&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;        let nevent = utils::new_nevent(event_id, &amp;amp;success?)?;&lt;br/&gt;        println!(&amp;#34;Comment created: {nevent}&amp;#34;);&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Creates a quoted reply string in the format &amp;#34;On yyyy-mm-dd at hh:mm UTC,&lt;br/&gt;/// {author} wrote:&amp;#34; followed by the event content. Uses display name if&lt;br/&gt;/// available, otherwise falls back to a shortened npub string. Dates are&lt;br/&gt;/// formatted in UTC.&lt;br/&gt;async fn quote_reply_to_content(client: &amp;amp;NostrClient, quoted_event: &amp;amp;Event) -&amp;gt; String {&lt;br/&gt;    let author_name = client.get_username(quoted_event.pubkey).await;&lt;br/&gt;&lt;br/&gt;    let fdate = chrono::DateTime::from_timestamp(&lt;br/&gt;        quoted_event&lt;br/&gt;            .created_at&lt;br/&gt;            .as_u64()&lt;br/&gt;            .try_into()&lt;br/&gt;            .unwrap_or(MAX_DATE),&lt;br/&gt;        0,&lt;br/&gt;    )&lt;br/&gt;    .map(|datetime| datetime.format(&amp;#34;On %F at %R UTC, &amp;#34;).to_string())&lt;br/&gt;    .unwrap_or_default();&lt;br/&gt;&lt;br/&gt;    format!(&lt;br/&gt;        &amp;#34;{fdate}{author_name} wrote:\n&amp;gt; {}&amp;#34;,&lt;br/&gt;        quoted_event.content.trim().replace(&amp;#34;\n&amp;#34;, &amp;#34;\n&amp;gt; &amp;#34;)&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Gets the repository coordinate from a root Nostr event&amp;#39;s tags.&lt;br/&gt;/// The event must contain a coordinate tag with GitRepoAnnouncement kind.&lt;br/&gt;fn coordinates_from_root(root: &amp;amp;Event) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    let coordinates: Vec&amp;lt;Coordinate&amp;gt; = root&lt;br/&gt;        .tags&lt;br/&gt;        .coordinates()&lt;br/&gt;        .filter(|c| c.kind == Kind::GitRepoAnnouncement)&lt;br/&gt;        .cloned()&lt;br/&gt;        .collect();&lt;br/&gt;&lt;br/&gt;    if coordinates.is_empty() {&lt;br/&gt;        return Err(N34Error::InvalidEvent(&lt;br/&gt;            &amp;#34;The Git issue/patch does not specify a target repository&amp;#34;.to_owned(),&lt;br/&gt;        ));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    Ok(coordinates)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns the event author read relays if found, otherwise an empty vector&lt;br/&gt;async fn event_author_read_relays(client: &amp;amp;NostrClient, event: Option&amp;lt;&amp;amp;Event&amp;gt;) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;    if let Some(root_event) = event {&lt;br/&gt;        client.read_relays_from_user(root_event.pubkey).await&lt;br/&gt;    } else {&lt;br/&gt;        Vec::new()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:51:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfp30af0zkj4usldysha9658d6g4ejv7jsuhy3qx90clc680ju5eczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrshyn37l</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfp30af0zkj4usldysha9658d6g4ejv7jsuhy3qx90clc680ju5eczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrshyn37l" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use super::*;&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_normal() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 24e8522268ad675996fc3b35209ce23951236bdc Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 27 May 2025 19:20:42 &#43;0000&lt;br/&gt;Subject: [PATCH] chore: a to abc&lt;br/&gt;&lt;br/&gt;Abc patch&lt;br/&gt;---&lt;br/&gt; src/nostr_utils/mod.rs            |  1 &#43;&lt;br/&gt; 1files changed, 3 insertions(&#43;), 1 deletions(-)&lt;br/&gt;&lt;br/&gt;diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs&lt;br/&gt;index 4120f5a..e68783c 100644&lt;br/&gt;--- a/src/nostr_utils/mod.rs&lt;br/&gt;&#43;&#43;&#43; b/src/nostr_utils/mod.rs&lt;br/&gt;@@ -103,31 &#43;103,9 @@ impl CommandRunner for NewArgs {&lt;br/&gt;&lt;br/&gt;- a&lt;br/&gt;&#43; abc&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(patch.subject, &amp;#34;[PATCH] chore: a to abc&amp;#34;);&lt;br/&gt;    assert_eq!(patch.body, &amp;#34;Abc patch&amp;#34;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_normal_with_patch_in_content() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 24e8522268ad675996fc3b35209ce23951236bdc Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 27 May 2025 19:20:42 &#43;0000&lt;br/&gt;Subject: [PATCH] chore: Subject in subject&lt;br/&gt;&lt;br/&gt;A good test patch&lt;br/&gt;---&lt;br/&gt; src/nostr_utils/mod.rs            |  1 &#43;&lt;br/&gt; 1files changed, 3 insertions(&#43;), 1 deletions(-)&lt;br/&gt;&lt;br/&gt;diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs&lt;br/&gt;index 4120f5a..e68783c 100644&lt;br/&gt;--- a/src/nostr_utils/mod.rs&lt;br/&gt;&#43;&#43;&#43; b/src/nostr_utils/mod.rs&lt;br/&gt;@@ -103,31 &#43;103,9 @@ impl CommandRunner for NewArgs {&lt;br/&gt;&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 27 May 2025 19:20:42 &#43;0000&lt;br/&gt;Subject: [PATCH] chore: What a subject&lt;br/&gt;&lt;br/&gt;hi&lt;br/&gt;---&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(patch.subject, &amp;#34;[PATCH] chore: Subject in subject&amp;#34;);&lt;br/&gt;    assert_eq!(patch.body, &amp;#34;A good test patch&amp;#34;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_multiline_subject() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 24e8522268ad675996fc3b35209ce23951236bdc Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 27 May 2025 19:20:42 &#43;0000&lt;br/&gt;Subject: [PATCH] chore: Some long subject yes so long one Some long subject yes&lt;br/&gt; so long one&lt;br/&gt;&lt;br/&gt;Abc patch&lt;br/&gt;---&lt;br/&gt; src/nostr_utils/mod.rs            |  1 &#43;&lt;br/&gt; 1files changed, 3 insertions(&#43;), 1 deletions(-)&lt;br/&gt;&lt;br/&gt;diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs&lt;br/&gt;index 4120f5a..e68783c 100644&lt;br/&gt;--- a/src/nostr_utils/mod.rs&lt;br/&gt;&#43;&#43;&#43; b/src/nostr_utils/mod.rs&lt;br/&gt;@@ -103,31 &#43;103,9 @@ impl CommandRunner for NewArgs {&lt;br/&gt;&lt;br/&gt;- a&lt;br/&gt;&#43; abc&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.subject,&lt;br/&gt;        &amp;#34;[PATCH] chore: Some long subject yes so long one Some long subject yes so long one&amp;#34;&lt;br/&gt;    );&lt;br/&gt;    assert_eq!(patch.body, &amp;#34;Abc patch&amp;#34;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_multiline_body() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 24e8522268ad675996fc3b35209ce23951236bdc Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 27 May 2025 19:20:42 &#43;0000&lt;br/&gt;Subject: [PATCH] chore: a to abc&lt;br/&gt;&lt;br/&gt;Lorem ipsum dolor sit amet. 33 laborum galisum aut fugiat dicta vel accusamus&lt;br/&gt;aliquam vel quisquam fuga in incidunt voluptas a aliquid neque ab iure pariatur.&lt;br/&gt;Et molestiae vero a consectetur laborum et accusantium sequi. Et ratione&lt;br/&gt;atque et molestiae dolorem in asperiores amet id dolor corporis in adipisci&lt;br/&gt;aspernatur.&lt;br/&gt;---&lt;br/&gt; src/nostr_utils/mod.rs            |  1 &#43;&lt;br/&gt; 1files changed, 3 insertions(&#43;), 1 deletions(-)&lt;br/&gt;&lt;br/&gt;diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs&lt;br/&gt;index 4120f5a..e68783c 100644&lt;br/&gt;--- a/src/nostr_utils/mod.rs&lt;br/&gt;&#43;&#43;&#43; b/src/nostr_utils/mod.rs&lt;br/&gt;@@ -103,31 &#43;103,9 @@ impl CommandRunner for NewArgs {&lt;br/&gt;&lt;br/&gt;- a&lt;br/&gt;&#43; abc&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(patch.subject, &amp;#34;[PATCH] chore: a to abc&amp;#34;);&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.body,&lt;br/&gt;        &amp;#34;Lorem ipsum dolor sit amet. 33 laborum galisum aut fugiat dicta vel accusamus&lt;br/&gt;aliquam vel quisquam fuga in incidunt voluptas a aliquid neque ab iure pariatur.&lt;br/&gt;Et molestiae vero a consectetur laborum et accusantium sequi. Et ratione&lt;br/&gt;atque et molestiae dolorem in asperiores amet id dolor corporis in adipisci&lt;br/&gt;aspernatur.&amp;#34;&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_cover_letter() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 864f3018f62ab2e1265edb670d5493dafe7d2cb2 Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 3 Jun 2025 08:41:12 &#43;0000&lt;br/&gt;Subject: [PATCH v2 0/7] feat: Some test just a test&lt;br/&gt;&lt;br/&gt;Cover body&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(patch.subject, &amp;#34;[PATCH v2 0/7] feat: Some test just a test&amp;#34;);&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.body,&lt;br/&gt;        &amp;#34;Cover body&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&amp;#34;&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_multiline_cover_subject() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 864f3018f62ab2e1265edb670d5493dafe7d2cb2 Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 3 Jun 2025 08:41:12 &#43;0000&lt;br/&gt;Subject: [PATCH v2 0/7] feat: Some test just a test some test just a test some&lt;br/&gt; test just a test&lt;br/&gt;&lt;br/&gt;Cover body&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.subject,&lt;br/&gt;        &amp;#34;[PATCH v2 0/7] feat: Some test just a test some test just a test some test just a test&amp;#34;&lt;br/&gt;    );&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.body,&lt;br/&gt;        &amp;#34;Cover body&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&amp;#34;&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_multiline_cover_body() {&lt;br/&gt;    let patch_content = r#&amp;#34;From 864f3018f62ab2e1265edb670d5493dafe7d2cb2 Mon Sep 17 00:00:00 2001&lt;br/&gt;From: Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;Date: Tue, 3 Jun 2025 08:41:12 &#43;0000&lt;br/&gt;Subject: [PATCH v2 0/7] feat: Some test just a test some test just a test some&lt;br/&gt; test just a test&lt;br/&gt;&lt;br/&gt;Lorem ipsum dolor sit amet. 33 laborum galisum aut fugiat dicta vel accusamus&lt;br/&gt;aliquam vel quisquam fuga in incidunt voluptas a aliquid neque ab iure pariatur.&lt;br/&gt;Et molestiae vero a consectetur laborum et accusantium sequi. Et ratione&lt;br/&gt;atque et molestiae dolorem in asperiores amet id dolor corporis in adipisci&lt;br/&gt;aspernatur.&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&lt;br/&gt;-- &lt;br/&gt;2.49.0&amp;#34;#;&lt;br/&gt;    let patch = GitPatch::from_str(patch_content).unwrap();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.subject,&lt;br/&gt;        &amp;#34;[PATCH v2 0/7] feat: Some test just a test some test just a test some test just a test&amp;#34;&lt;br/&gt;    );&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.body,&lt;br/&gt;        &amp;#34;Lorem ipsum dolor sit amet. 33 laborum galisum aut fugiat dicta vel accusamus&lt;br/&gt;aliquam vel quisquam fuga in incidunt voluptas a aliquid neque ab iure pariatur.&lt;br/&gt;Et molestiae vero a consectetur laborum et accusantium sequi. Et ratione&lt;br/&gt;atque et molestiae dolorem in asperiores amet id dolor corporis in adipisci&lt;br/&gt;aspernatur.&lt;br/&gt;&lt;br/&gt;Awiteb (1):&lt;br/&gt;  chore: Update `README.md`&lt;br/&gt;&lt;br/&gt; README.md      |  2 &#43;-&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;base-commit: f670859b92d525874fd621452080c8479964ac6a&amp;#34;&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn normal_patch_filename() {&lt;br/&gt;    let mut patch = GitPatch {&lt;br/&gt;        inner:   String::new(),&lt;br/&gt;        subject: String::new(),&lt;br/&gt;        body:    String::new(),&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v2 0/3] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;v2-0000-cover-letter.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH 0/3] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;0000-cover-letter.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v2 1/3] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;v2-0001-feat-some-test-just-a-test.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v42 1/3] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;v42-0001-feat-some-test-just-a-test.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v42 23/30] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;v42-0023-feat-some-test-just-a-test.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH 1/3] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;0001-feat-some-test-just-a-test.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH 32/50] feat: Some test just a test&amp;#34;.to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;0032-feat-some-test-just-a-test.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v100 32/50] feat: some long subject some long subject some long \&lt;br/&gt;                     subject some long subject&amp;#34;&lt;br/&gt;        .to_owned();&lt;br/&gt;    assert_eq!(&lt;br/&gt;        patch.filename(&amp;#34;&amp;#34;).unwrap(),&lt;br/&gt;        PathBuf::from(&amp;#34;v100-0032-feat-some-long-subject-some-long-subject-some-long-subject.patch&amp;#34;)&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_filename_without_patch() {&lt;br/&gt;    let mut patch = GitPatch {&lt;br/&gt;        inner:   String::new(),&lt;br/&gt;        subject: &amp;#34;[RFC v5 1/2] Something&amp;#34;.to_owned(),&lt;br/&gt;        body:    String::new(),&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    assert!(patch.filename(&amp;#34;&amp;#34;).is_err());&lt;br/&gt;    patch.subject = &amp;#34;Something&amp;#34;.to_owned();&lt;br/&gt;    assert!(patch.filename(&amp;#34;&amp;#34;).is_err());&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_filename_without_number() {&lt;br/&gt;    let mut patch = GitPatch {&lt;br/&gt;        inner:   String::new(),&lt;br/&gt;        subject: &amp;#34;[PATCH v5 /2] Something&amp;#34;.to_owned(),&lt;br/&gt;        body:    String::new(),&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    assert!(patch.filename(&amp;#34;&amp;#34;).is_err());&lt;br/&gt;    patch.subject = &amp;#34;[PATCH v5 2/] Something&amp;#34;.to_owned();&lt;br/&gt;    assert!(patch.filename(&amp;#34;&amp;#34;).is_err());&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn patch_filename_without_version() {&lt;br/&gt;    let patch = GitPatch {&lt;br/&gt;        inner:   String::new(),&lt;br/&gt;        subject: &amp;#34;[PATCH 1/2] Something&amp;#34;.to_owned(),&lt;br/&gt;        body:    String::new(),&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    assert!(patch.filename(&amp;#34;&amp;#34;).is_ok());&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:51:27&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqnjzchs4a3haglxx90xekcfhux6t3ujqayu0qm97vrqfsh2h4rkszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrswas9r8</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqnjzchs4a3haglxx90xekcfhux6t3ujqayu0qm97vrqfsh2h4rkszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrswas9r8" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{fs, str::FromStr};&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{EventBuilder, EventId, Kind, Tag, TagKind, Tags, UnsignedEvent},&lt;br/&gt;    hashes::sha1::Hash as Sha1Hash,&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{nip01::Coordinate, nip10::Marker},&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use super::GitPatch;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        patch::{REVISION_ROOT_HASHTAG_CONTENT, ROOT_HASHTAG_CONTENT},&lt;br/&gt;        traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;    nostr_utils::{&lt;br/&gt;        NostrClient,&lt;br/&gt;        traits::{NaddrsUtils, ReposUtils},&lt;br/&gt;        utils,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Prefix used for git patch alt.&lt;br/&gt;const PATCH_ALT_PREFIX: &amp;amp;str = &amp;#34;git patch: &amp;#34;;&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct SendArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:         Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// List of patch files to send (space separated).&lt;br/&gt;    ///&lt;br/&gt;    /// For p-tagging users, include them in the cover letter with&lt;br/&gt;    /// `nostr:npub1...`.&lt;br/&gt;    #[arg(value_name = &amp;#34;PATCH-PATH&amp;#34;, required = true, value_parser = parse_patch_path)]&lt;br/&gt;    patches:        Vec&amp;lt;GitPatch&amp;gt;,&lt;br/&gt;    /// Original patch ID if this is a revision of it&lt;br/&gt;    #[arg(long, value_name = &amp;#34;EVENT-ID&amp;#34;)]&lt;br/&gt;    original_patch: Option&amp;lt;NostrEvent&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for SendArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(&lt;br/&gt;            self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;            &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;        )?)?;&lt;br/&gt;&lt;br/&gt;        let repo_coordinates = naddrs.clone().into_coordinates();&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        let user_pubk = client.pubkey().await?;&lt;br/&gt;&lt;br/&gt;        client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;        if let Some(original_patch) = &amp;amp;self.original_patch {&lt;br/&gt;            client.add_relays(&amp;amp;original_patch.relays).await;&lt;br/&gt;        }&lt;br/&gt;        let relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;        client&lt;br/&gt;            .add_relays(&amp;amp;utils::add_read_relays(relays_list.as_ref()))&lt;br/&gt;            .await;&lt;br/&gt;        let repos = client.fetch_repos(&amp;amp;repo_coordinates).await?;&lt;br/&gt;        let euc = repos.extract_euc();&lt;br/&gt;        let maintainers = repos.extract_maintainers();&lt;br/&gt;        client.add_relays(&amp;amp;repos.extract_relays()).await;&lt;br/&gt;&lt;br/&gt;        let (events, events_write_relays) = make_patch_series(&lt;br/&gt;            &amp;amp;client,&lt;br/&gt;            self.patches,&lt;br/&gt;            self.original_patch.as_ref().map(|e| e.event_id),&lt;br/&gt;            repos.extract_relays().first().cloned(),&lt;br/&gt;            repo_coordinates,&lt;br/&gt;            euc,&lt;br/&gt;            user_pubk,&lt;br/&gt;        )&lt;br/&gt;        .await?;&lt;br/&gt;&lt;br/&gt;        let write_relays = [&lt;br/&gt;            relays,&lt;br/&gt;            repos.extract_relays(),&lt;br/&gt;            events_write_relays,&lt;br/&gt;            naddrs.extract_relays(),&lt;br/&gt;            self.original_patch.map(|e| e.relays).unwrap_or_default(),&lt;br/&gt;            utils::add_write_relays(relays_list.as_ref()),&lt;br/&gt;            client.read_relays_from_users(&amp;amp;maintainers).await,&lt;br/&gt;        ]&lt;br/&gt;        .concat();&lt;br/&gt;&lt;br/&gt;        tracing::trace!(write_relays = ?write_relays, &amp;#34;Write relays of the patches&amp;#34;);&lt;br/&gt;        let nevents = future::join_all(events.into_iter().map(|mut event| {&lt;br/&gt;            async {&lt;br/&gt;                let event_id = event.id();&lt;br/&gt;                let subject = event&lt;br/&gt;                    .tags&lt;br/&gt;                    .find(TagKind::Alt)&lt;br/&gt;                    .and_then(Tag::content)&lt;br/&gt;                    .expect(&amp;#34;There is an alt&amp;#34;)&lt;br/&gt;                    .replace(PATCH_ALT_PREFIX, &amp;#34;&amp;#34;);&lt;br/&gt;                client&lt;br/&gt;                    .send_event_to(event, relays_list.as_ref(), &amp;amp;write_relays)&lt;br/&gt;                    .await&lt;br/&gt;                    .map(|r| Ok((subject, utils::new_nevent(event_id, &amp;amp;r)?)))?&lt;br/&gt;            }&lt;br/&gt;        }))&lt;br/&gt;        .await&lt;br/&gt;        .into_iter()&lt;br/&gt;        .collect::&amp;lt;N34Result&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;&amp;gt;()?;&lt;br/&gt;&lt;br/&gt;        for (subject, nevent) in nevents {&lt;br/&gt;            println!(&amp;#34;Created &amp;#39;{subject}&amp;#39;: {nevent}&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;fn parse_patch_path(patch_path: &amp;amp;str) -&amp;gt; Result&amp;lt;GitPatch, String&amp;gt; {&lt;br/&gt;    tracing::debug!(&amp;#34;Parsing patch file `{patch_path}`&amp;#34;);&lt;br/&gt;    let patch_content = fs::read_to_string(patch_path)&lt;br/&gt;        .map_err(|err| format!(&amp;#34;Failed to read patch file `{patch_path}`: {err}&amp;#34;))?;&lt;br/&gt;    GitPatch::from_str(&amp;amp;patch_content)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;async fn make_patch_series(&lt;br/&gt;    client: &amp;amp;NostrClient,&lt;br/&gt;    patches: Vec&amp;lt;GitPatch&amp;gt;,&lt;br/&gt;    original_patch: Option&amp;lt;EventId&amp;gt;,&lt;br/&gt;    relay_hint: Option&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;    repo_coordinates: Vec&amp;lt;Coordinate&amp;gt;,&lt;br/&gt;    euc: Option&amp;lt;&amp;amp;Sha1Hash&amp;gt;,&lt;br/&gt;    author_pkey: PublicKey,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;(Vec&amp;lt;UnsignedEvent&amp;gt;, Vec&amp;lt;RelayUrl&amp;gt;)&amp;gt; {&lt;br/&gt;    let mut write_relays = Vec::new();&lt;br/&gt;    let mut patch_series = Vec::new();&lt;br/&gt;    let mut patches = patches.into_iter();&lt;br/&gt;    let root_patch = patches.next().expect(&amp;#34;Patches can&amp;#39;t be empty&amp;#34;);&lt;br/&gt;    let (root_event, root_relays) = make_patch(&lt;br/&gt;        client,&lt;br/&gt;        root_patch,&lt;br/&gt;        None,&lt;br/&gt;        original_patch,&lt;br/&gt;        relay_hint.as_ref(),&lt;br/&gt;        &amp;amp;repo_coordinates,&lt;br/&gt;        euc,&lt;br/&gt;        author_pkey,&lt;br/&gt;    )&lt;br/&gt;    .await;&lt;br/&gt;    write_relays.extend(root_relays);&lt;br/&gt;    let root_id = *root_event.id.as_ref().expect(&amp;#34;There is an id&amp;#34;);&lt;br/&gt;    let mut previous_patch = root_id;&lt;br/&gt;    patch_series.push(root_event);&lt;br/&gt;&lt;br/&gt;    for patch in patches {&lt;br/&gt;        let (patch_event, patch_relays) = make_patch(&lt;br/&gt;            client,&lt;br/&gt;            patch,&lt;br/&gt;            Some(root_id),&lt;br/&gt;            Some(previous_patch),&lt;br/&gt;            relay_hint.as_ref(),&lt;br/&gt;            &amp;amp;repo_coordinates,&lt;br/&gt;            euc,&lt;br/&gt;            author_pkey,&lt;br/&gt;        )&lt;br/&gt;        .await;&lt;br/&gt;        previous_patch = patch_event.id.expect(&amp;#34;there is an id&amp;#34;);&lt;br/&gt;        write_relays.extend(patch_relays);&lt;br/&gt;        patch_series.push(patch_event);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    Ok((patch_series, write_relays))&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[allow(clippy::too_many_arguments)]&lt;br/&gt;async fn make_patch(&lt;br/&gt;    client: &amp;amp;NostrClient,&lt;br/&gt;    patch: GitPatch,&lt;br/&gt;    root: Option&amp;lt;EventId&amp;gt;,&lt;br/&gt;    reply_to: Option&amp;lt;EventId&amp;gt;,&lt;br/&gt;    write_relay: Option&amp;lt;&amp;amp;RelayUrl&amp;gt;,&lt;br/&gt;    repo_coordinates: &amp;amp;[Coordinate],&lt;br/&gt;    euc: Option&amp;lt;&amp;amp;Sha1Hash&amp;gt;,&lt;br/&gt;    author_pkey: PublicKey,&lt;br/&gt;) -&amp;gt; (UnsignedEvent, Vec&amp;lt;RelayUrl&amp;gt;) {&lt;br/&gt;    let content_details = client.parse_content(&amp;amp;patch.body).await;&lt;br/&gt;    let content_relays = content_details.write_relays.clone();&lt;br/&gt;    // NIP-34 compliance requires referencing the previous patch using `NIP-10 e&lt;br/&gt;    // reply`. However, this fails for the second patch when&lt;br/&gt;    // `EventBuilder::dedup_tags` is enabled because:&lt;br/&gt;    // 1. The tag is treated as a duplicate based on its content (the root ID).&lt;br/&gt;    // 2. The second patch would reply to the root twice:&lt;br/&gt;    //    - First with the &amp;#39;root&amp;#39; marker&lt;br/&gt;    //    - Then with the &amp;#39;reply&amp;#39; marker&lt;br/&gt;    // The `EventBuilder::dedup_tags` function then removes the &amp;#39;reply&amp;#39; marker as a&lt;br/&gt;    // duplicate.&lt;br/&gt;    let mut safe_dedup_tags = Tags::new();&lt;br/&gt;    safe_dedup_tags.push(Tag::alt(format!(&amp;#34;{PATCH_ALT_PREFIX}{}&amp;#34;, patch.subject)));&lt;br/&gt;    safe_dedup_tags.push(Tag::description(patch.subject));&lt;br/&gt;    safe_dedup_tags.extend(content_details.into_tags());&lt;br/&gt;    safe_dedup_tags.extend(&lt;br/&gt;        repo_coordinates&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|c| Tag::coordinate(c.clone(), None)),&lt;br/&gt;    );&lt;br/&gt;    safe_dedup_tags.extend(&lt;br/&gt;        repo_coordinates&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|c| Tag::public_key(c.public_key)),&lt;br/&gt;    );&lt;br/&gt;    if let Some(euc) = euc {&lt;br/&gt;        safe_dedup_tags.push(Tag::reference(euc.to_string()));&lt;br/&gt;    }&lt;br/&gt;    safe_dedup_tags.dedup();&lt;br/&gt;    let mut event_builder = EventBuilder::new(Kind::GitPatch, patch.inner).tags(safe_dedup_tags);&lt;br/&gt;&lt;br/&gt;    // If the root is None, this indicates we&amp;#39;re handling the root event&lt;br/&gt;    if let Some(root_id) = root {&lt;br/&gt;        event_builder =&lt;br/&gt;            event_builder.tag(utils::event_reply_tag(&amp;amp;root_id, write_relay, Marker::Root));&lt;br/&gt;    } else {&lt;br/&gt;        event_builder = event_builder.tag(Tag::hashtag(ROOT_HASHTAG_CONTENT));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // Handles the case where there is a patch to reply to but no root. This&lt;br/&gt;    // indicates we are processing a revision, as the root revision should reply&lt;br/&gt;    // directly to the original patch.&lt;br/&gt;    if let Some(reply_to_id) = reply_to {&lt;br/&gt;        if root.is_none() {&lt;br/&gt;            event_builder = event_builder.tags([&lt;br/&gt;                utils::event_reply_tag(&amp;amp;reply_to_id, write_relay, Marker::Reply),&lt;br/&gt;                Tag::hashtag(REVISION_ROOT_HASHTAG_CONTENT),&lt;br/&gt;            ]);&lt;br/&gt;        } else {&lt;br/&gt;            event_builder = event_builder.tag(utils::event_reply_tag(&lt;br/&gt;                &amp;amp;reply_to_id,&lt;br/&gt;                write_relay,&lt;br/&gt;                Marker::Reply,&lt;br/&gt;            ));&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    (&lt;br/&gt;        event_builder.build(author_pkey),&lt;br/&gt;        content_relays.into_iter().collect(),&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:51:14&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsz22eml09cmje4tupl3jx3vnrq5s9f26u39hepg69pvzarwpmad9czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs5s8xzs</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsz22eml09cmje4tupl3jx3vnrq5s9f26u39hepg69pvzarwpmad9czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs5s8xzs" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::PatchStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ReopenArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The closed/drafted patch id to reopen it. Must be orignal root patch&lt;br/&gt;    patch_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ReopenArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::patch_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.patch_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            PatchStatus::Open,&lt;br/&gt;            None,&lt;br/&gt;            Vec::new(),&lt;br/&gt;            |patch_status| {&lt;br/&gt;                if patch_status.is_open() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t open an already open patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_merged_or_applied() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t open a merged/applied patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:51:03&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgft2zdp9f33g5pt88vnj5sgsy20fjhjnckhrdj7dnrz8ryt8sp7czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfw9lc2</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgft2zdp9f33g5pt88vnj5sgsy20fjhjnckhrdj7dnrz8ryt8sp7czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfw9lc2" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `patch apply` suubcommand&lt;br/&gt;mod apply;&lt;br/&gt;/// `patch close` subcommand&lt;br/&gt;mod close;&lt;br/&gt;/// `patch draft` subcommand&lt;br/&gt;mod draft;&lt;br/&gt;/// `patch fetch` subcommand&lt;br/&gt;mod fetch;&lt;br/&gt;/// `patch list` subcommand&lt;br/&gt;mod list;&lt;br/&gt;/// `patch merge` subcommand&lt;br/&gt;mod merge;&lt;br/&gt;/// `patch reopen` subcommand&lt;br/&gt;mod reopen;&lt;br/&gt;/// `patch send` subcommand&lt;br/&gt;mod send;&lt;br/&gt;#[cfg(test)]&lt;br/&gt;mod tests;&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    fmt,&lt;br/&gt;    path::{Path, PathBuf},&lt;br/&gt;    str::FromStr,&lt;br/&gt;    sync::LazyLock,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Subcommand;&lt;br/&gt;use nostr::event::Kind;&lt;br/&gt;use regex::Regex;&lt;br/&gt;&lt;br/&gt;use self::apply::ApplyArgs;&lt;br/&gt;use self::close::CloseArgs;&lt;br/&gt;use self::draft::DraftArgs;&lt;br/&gt;use self::fetch::FetchArgs;&lt;br/&gt;use self::list::ListArgs;&lt;br/&gt;use self::merge::MergeArgs;&lt;br/&gt;use self::reopen::ReopenArgs;&lt;br/&gt;use self::send::SendArgs;&lt;br/&gt;use super::{CliOptions, CommandRunner};&lt;br/&gt;use crate::error::{N34Error, N34Result};&lt;br/&gt;&lt;br/&gt;/// Regular expression for extracting the patch subject.&lt;br/&gt;static SUBJECT_RE: LazyLock&amp;lt;Regex&amp;gt; =&lt;br/&gt;    LazyLock::new(|| Regex::new(r&amp;#34;(?m)^Subject: (.*(?:\n .*)*)&amp;#34;).unwrap());&lt;br/&gt;&lt;br/&gt;/// Regular expression for extracting the patch body.&lt;br/&gt;static BODY_RE: LazyLock&amp;lt;Regex&amp;gt; =&lt;br/&gt;    LazyLock::new(|| Regex::new(r&amp;#34;\n\n((?:.|\n)*?)(?:\n--[ -]|\z)&amp;#34;).unwrap());&lt;br/&gt;&lt;br/&gt;/// Regular expiration for extracting the patch version and number&lt;br/&gt;static PATCH_VERSION_NUMBER_RE: LazyLock&amp;lt;Regex&amp;gt; = LazyLock::new(|| {&lt;br/&gt;    Regex::new(r&amp;#34;\[PATCH\s&#43;(?:v(?&amp;lt;version&amp;gt;\d&#43;)\s*)?(?&amp;lt;number&amp;gt;\d&#43;)/(?:\d&#43;)&amp;#34;).unwrap()&lt;br/&gt;});&lt;br/&gt;&lt;br/&gt;/// Content of the hashtag representing the root patch.&lt;br/&gt;pub const ROOT_HASHTAG_CONTENT: &amp;amp;str = &amp;#34;root&amp;#34;;&lt;br/&gt;/// Content of the hashtag representing the root revision patch.&lt;br/&gt;pub const REVISION_ROOT_HASHTAG_CONTENT: &amp;amp;str = &amp;#34;root-revision&amp;#34;;&lt;br/&gt;/// The content of the hashtag used by `ngit-cli` to represent a root revision&lt;br/&gt;/// patch before the commit 6ae42e67d9da36f6c2e1356acba30a3a62112bc7. This was a&lt;br/&gt;/// typo.&lt;br/&gt;pub const LEGACY_NGIT_REVISION_ROOT_HASHTAG_CONTENT: &amp;amp;str = &amp;#34;revision-root&amp;#34;;&lt;br/&gt;&lt;br/&gt;#[derive(Subcommand, Debug)]&lt;br/&gt;pub enum PatchSubcommands {&lt;br/&gt;    /// Send one or more patches to a repository.&lt;br/&gt;    Send(SendArgs),&lt;br/&gt;    /// Fetches a patch by its id.&lt;br/&gt;    Fetch(FetchArgs),&lt;br/&gt;    /// Closes an open or drafted patch.&lt;br/&gt;    Close(CloseArgs),&lt;br/&gt;    /// Converts an open patch to draft state.&lt;br/&gt;    Draft(DraftArgs),&lt;br/&gt;    /// Reopens a closed or drafted patch.&lt;br/&gt;    Reopen(ReopenArgs),&lt;br/&gt;    /// Set an open patch status to applied.&lt;br/&gt;    Apply(ApplyArgs),&lt;br/&gt;    /// Set an open patch status to merged.&lt;br/&gt;    Merge(MergeArgs),&lt;br/&gt;    /// List the repositories patches.&lt;br/&gt;    List(ListArgs),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Represents a git patch&lt;br/&gt;#[derive(Clone, Debug)]&lt;br/&gt;pub struct GitPatch {&lt;br/&gt;    /// Full content of the patch file&lt;br/&gt;    pub inner:   String,&lt;br/&gt;    /// Short description of the patch changes&lt;br/&gt;    pub subject: String,&lt;br/&gt;    /// Detailed explanation of the patch changes&lt;br/&gt;    pub body:    String,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[derive(Debug)]&lt;br/&gt;pub enum PatchStatus {&lt;br/&gt;    /// The patch is currently open&lt;br/&gt;    Open,&lt;br/&gt;    /// The patch has been merged/applied&lt;br/&gt;    MergedApplied,&lt;br/&gt;    /// The patch has been closed&lt;br/&gt;    Closed,&lt;br/&gt;    /// A patch that has been drafted but not yet applied.&lt;br/&gt;    Draft,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl PatchStatus {&lt;br/&gt;    /// Maps the issue status to its corresponding Nostr kind.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn kind(&amp;amp;self) -&amp;gt; Kind {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Open =&amp;gt; Kind::GitStatusOpen,&lt;br/&gt;            Self::MergedApplied =&amp;gt; Kind::GitStatusApplied,&lt;br/&gt;            Self::Closed =&amp;gt; Kind::GitStatusClosed,&lt;br/&gt;            Self::Draft =&amp;gt; Kind::GitStatusDraft,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the string representation of the patch status.&lt;br/&gt;    pub const fn as_str(&amp;amp;self) -&amp;gt; &amp;amp;&amp;#39;static str {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Open =&amp;gt; &amp;#34;Open&amp;#34;,&lt;br/&gt;            Self::MergedApplied =&amp;gt; &amp;#34;Merged/Applied&amp;#34;,&lt;br/&gt;            Self::Closed =&amp;gt; &amp;#34;Closed&amp;#34;,&lt;br/&gt;            Self::Draft =&amp;gt; &amp;#34;Draft&amp;#34;,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the patch is open.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_open(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Open)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the patch is merged/applied.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_merged_or_applied(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::MergedApplied)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the patch is closed.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_closed(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Closed)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the patch is drafted&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_drafted(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Draft)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl From&amp;lt;&amp;amp;PatchStatus&amp;gt; for Kind {&lt;br/&gt;    fn from(status: &amp;amp;PatchStatus) -&amp;gt; Self {&lt;br/&gt;        status.kind()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl fmt::Display for PatchStatus {&lt;br/&gt;    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;&amp;#39;_&amp;gt;) -&amp;gt; fmt::Result {&lt;br/&gt;        write!(f, &amp;#34;{}&amp;#34;, self.as_str())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl TryFrom&amp;lt;Kind&amp;gt; for PatchStatus {&lt;br/&gt;    type Error = N34Error;&lt;br/&gt;&lt;br/&gt;    fn try_from(kind: Kind) -&amp;gt; Result&amp;lt;Self, Self::Error&amp;gt; {&lt;br/&gt;        match kind {&lt;br/&gt;            Kind::GitStatusOpen =&amp;gt; Ok(Self::Open),&lt;br/&gt;            Kind::GitStatusApplied =&amp;gt; Ok(Self::MergedApplied),&lt;br/&gt;            Kind::GitStatusClosed =&amp;gt; Ok(Self::Closed),&lt;br/&gt;            Kind::GitStatusDraft =&amp;gt; Ok(Self::Draft),&lt;br/&gt;            _ =&amp;gt; Err(N34Error::InvalidPatchStatus(kind)),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl GitPatch {&lt;br/&gt;    /// Returns the patch file name from the subject&lt;br/&gt;    pub fn filename(&amp;amp;self, parent: impl AsRef&amp;lt;Path&amp;gt;) -&amp;gt; N34Result&amp;lt;PathBuf&amp;gt; {&lt;br/&gt;        let (patch_version, patch_number) = if self.subject.contains(&amp;#34;[PATCH]&amp;#34;) {&lt;br/&gt;            (String::new(), &amp;#34;1&amp;#34;)&lt;br/&gt;        } else {&lt;br/&gt;            patch_version_and_subject(&amp;amp;self.subject)?&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        let patch_name = if patch_number == &amp;#34;0&amp;#34; {&lt;br/&gt;            &amp;#34;cover-letter&amp;#34;.to_owned()&lt;br/&gt;        } else {&lt;br/&gt;            patch_file_name(&amp;amp;self.subject)?&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        Ok(parent&lt;br/&gt;            .as_ref()&lt;br/&gt;            .join(format!(&amp;#34;{patch_version}{patch_number:0&amp;gt;4}-{patch_name}&amp;#34;).replace(&amp;#34;--&amp;#34;, &amp;#34;-&amp;#34;))&lt;br/&gt;            .with_extension(&amp;#34;patch&amp;#34;))&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for PatchSubcommands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::run_command!(self, options, &amp;amp; Send Fetch Close Reopen Draft Apply Merge List)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl FromStr for GitPatch {&lt;br/&gt;    type Err = String;&lt;br/&gt;&lt;br/&gt;    fn from_str(patch_content: &amp;amp;str) -&amp;gt; Result&amp;lt;Self, Self::Err&amp;gt; {&lt;br/&gt;        // Regex for subject (handles multi-line subjects)&lt;br/&gt;        let subject = SUBJECT_RE&lt;br/&gt;            .captures(patch_content)&lt;br/&gt;            .and_then(|cap| cap.get(1))&lt;br/&gt;            .ok_or(&amp;#34;No subject found&amp;#34;)?&lt;br/&gt;            .as_str()&lt;br/&gt;            .trim()&lt;br/&gt;            .replace(&amp;#39;\n&amp;#39;, &amp;#34;&amp;#34;)&lt;br/&gt;            .to_string();&lt;br/&gt;&lt;br/&gt;        // Regex for body&lt;br/&gt;        let body = BODY_RE&lt;br/&gt;            .captures(patch_content)&lt;br/&gt;            .and_then(|cap| cap.get(1))&lt;br/&gt;            .ok_or(&amp;#34;No body found&amp;#34;)?&lt;br/&gt;            .as_str()&lt;br/&gt;            .trim()&lt;br/&gt;            .to_string();&lt;br/&gt;        Ok(Self {&lt;br/&gt;            inner: patch_content.to_owned(),&lt;br/&gt;            subject,&lt;br/&gt;            body,&lt;br/&gt;        })&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts the version prefix and patch number from a patch subject string.&lt;br/&gt;///&lt;br/&gt;/// The version prefix is formatted as &amp;#34;v{version}-&amp;#34; if present, or an empty&lt;br/&gt;/// string. The patch number is mandatory and will cause an error if not found.&lt;br/&gt;fn patch_version_and_subject(subject: &amp;amp;str) -&amp;gt; N34Result&amp;lt;(String, &amp;amp;str)&amp;gt; {&lt;br/&gt;    let captures = PATCH_VERSION_NUMBER_RE.captures(subject).ok_or_else(|| {&lt;br/&gt;        N34Error::InvalidEvent(format!(&amp;#34;Can not parse the patch subject `{subject}`&amp;#34;))&lt;br/&gt;    })?;&lt;br/&gt;    Ok((&lt;br/&gt;        captures&lt;br/&gt;            .name(&amp;#34;version&amp;#34;)&lt;br/&gt;            .map(|m| format!(&amp;#34;v{}-&amp;#34;, m.as_str()))&lt;br/&gt;            .unwrap_or_default(),&lt;br/&gt;        captures&lt;br/&gt;            .name(&amp;#34;number&amp;#34;)&lt;br/&gt;            .map(|m| m.as_str())&lt;br/&gt;            .expect(&amp;#34;It&amp;#39;s not optional, regex will fail if it&amp;#39;s not found&amp;#34;),&lt;br/&gt;    ))&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts a clean file name from the patch subject by removing version info&lt;br/&gt;/// and special characters. Converts to lowercase and ensures the name only&lt;br/&gt;/// contains alphanumeric, &amp;#39;.&amp;#39;, &amp;#39;-&amp;#39;, or &amp;#39;_&amp;#39; characters.&lt;br/&gt;fn patch_file_name(subject: &amp;amp;str) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    Ok(subject&lt;br/&gt;        .split_once(&amp;#34;]&amp;#34;)&lt;br/&gt;        .ok_or_else(|| {&lt;br/&gt;            N34Error::InvalidEvent(format!(&lt;br/&gt;                &amp;#34;Invalid patch subject. No `[PATCH ...]`: `{subject}`&amp;#34;,&lt;br/&gt;            ))&lt;br/&gt;        })?&lt;br/&gt;        .1&lt;br/&gt;        .trim()&lt;br/&gt;        .to_lowercase()&lt;br/&gt;        .replace(&lt;br/&gt;            |c: char| !c.is_ascii_alphanumeric() &amp;amp;&amp;amp; ![&amp;#39;.&amp;#39;, &amp;#39;-&amp;#39;, &amp;#39;_&amp;#39;].contains(&amp;amp;c),&lt;br/&gt;            &amp;#34;-&amp;#34;,&lt;br/&gt;        )&lt;br/&gt;        .chars()&lt;br/&gt;        .take(60)&lt;br/&gt;        .collect::&amp;lt;String&amp;gt;()&lt;br/&gt;        .trim_matches(&amp;#39;-&amp;#39;)&lt;br/&gt;        .trim()&lt;br/&gt;        .replace(&amp;#34;--&amp;#34;, &amp;#34;-&amp;#34;))&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:50:51&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqszmv5eejxfhfrxuu9nw3r0m77lum7x28j5dh2dynleqd94xc64jmszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvyqyzz</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqszmv5eejxfhfrxuu9nw3r0m77lum7x28j5dh2dynleqd94xc64jmszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsvyqyzz" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::hashes::sha1::Hash as Sha1Hash;&lt;br/&gt;&lt;br/&gt;use super::PatchStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, VecNostrEventExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct MergeArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:         Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The open patch id to merge it. Must be orignal root patch or&lt;br/&gt;    /// revision root&lt;br/&gt;    patch_id:       NostrEvent,&lt;br/&gt;    /// Patches that have been merged. Use this when only some patches have been&lt;br/&gt;    /// merged, not all.&lt;br/&gt;    #[arg(long = &amp;#34;patches&amp;#34;, value_name = &amp;#34;PATCH-EVENT-ID&amp;#34;)]&lt;br/&gt;    merged_patches: Vec&amp;lt;NostrEvent&amp;gt;,&lt;br/&gt;    /// The merge commit id&lt;br/&gt;    merge_commit:   Sha1Hash,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for MergeArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::patch_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.patch_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            PatchStatus::MergedApplied,&lt;br/&gt;            Some(either::Either::Left(self.merge_commit)),&lt;br/&gt;            self.merged_patches.into_event_ids(),&lt;br/&gt;            |patch_status| {&lt;br/&gt;                if patch_status.is_merged_or_applied() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t merge an already merged patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_closed() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t merge a closed patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_drafted() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t merge a drafted patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:50:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs9n79krzddmt85m695dfm4jzg7vdn86yg3csd25jjp7ayha5auxsszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrseygcht</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs9n79krzddmt85m695dfm4jzg7vdn86yg3csd25jjp7ayha5auxsszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrseygcht" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::num::NonZeroUsize;&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, common_commands, traits::CommandRunner, types::NaddrOrSet},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ListArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;)]&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// Maximum number of patches to list&lt;br/&gt;    #[arg(long, default_value = &amp;#34;15&amp;#34;)]&lt;br/&gt;    limit:  NonZeroUsize,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ListArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        common_commands::list_patches_and_issues(options, self.naddrs, true, self.limit.into())&lt;br/&gt;            .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:50:30&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs03vxcquj8t6q3n6k88l6y0h2fwntzad3uvxzua8lcfn4w9mcsn0qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2lahs0</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs03vxcquj8t6q3n6k88l6y0h2fwntzad3uvxzua8lcfn4w9mcsn0qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2lahs0" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    fs,&lt;br/&gt;    path::{Path, PathBuf},&lt;br/&gt;    str::FromStr,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Kind, TagKind},&lt;br/&gt;    filter::Filter,&lt;br/&gt;    nips::nip19::ToBech32,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;    nostr_utils::{&lt;br/&gt;        NostrClient,&lt;br/&gt;        traits::{NaddrsUtils, ReposUtils},&lt;br/&gt;        utils,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct FetchArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// Output directory for the patches. Default to the current directory&lt;br/&gt;    #[arg(short, long, value_name = &amp;#34;PATH&amp;#34;)]&lt;br/&gt;    output:   Option&amp;lt;PathBuf&amp;gt;,&lt;br/&gt;    /// The patch id to fetch it&lt;br/&gt;    patch_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for FetchArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = utils::naddrs_or_file(&lt;br/&gt;            self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;            &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;        )?;&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        let output_path = self.output.unwrap_or_default();&lt;br/&gt;&lt;br/&gt;        client&lt;br/&gt;            .add_relays(&lt;br/&gt;                &amp;amp;[&lt;br/&gt;                    naddrs.extract_relays(),&lt;br/&gt;                    self.patch_id.relays,&lt;br/&gt;                    client&lt;br/&gt;                        .fetch_repos(&amp;amp;naddrs.into_coordinates())&lt;br/&gt;                        .await?&lt;br/&gt;                        .extract_relays(),&lt;br/&gt;                ]&lt;br/&gt;                .concat(),&lt;br/&gt;            )&lt;br/&gt;            .await;&lt;br/&gt;&lt;br/&gt;        let root_patch = client&lt;br/&gt;            .fetch_event(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .id(self.patch_id.event_id)&lt;br/&gt;                    .kind(Kind::GitPatch),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .ok_or(N34Error::CanNotFoundPatch)?;&lt;br/&gt;&lt;br/&gt;        if !root_patch&lt;br/&gt;            .tags&lt;br/&gt;            .iter()&lt;br/&gt;            .any(|t| t.kind() == TagKind::t() &amp;amp;&amp;amp; t.content().is_some_and(|c| c == &amp;#34;root&amp;#34;))&lt;br/&gt;        {&lt;br/&gt;            return Err(N34Error::NotRootPatch);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let root_author = root_patch.pubkey;&lt;br/&gt;        let root_patch = super::GitPatch::from_str(&amp;amp;root_patch.content)&lt;br/&gt;            .map_err(|err| N34Error::InvalidEvent(format!(&amp;#34;Failed to parse the patch: {err}&amp;#34;)))?;&lt;br/&gt;&lt;br/&gt;        tracing::info!(&amp;#34;Found the root patch: `{}`&amp;#34;, root_patch.subject);&lt;br/&gt;&lt;br/&gt;        let mut patches = client&lt;br/&gt;            .fetch_patch_series(self.patch_id.event_id, root_author)&lt;br/&gt;            .await?&lt;br/&gt;            .into_iter()&lt;br/&gt;            .map(|p| {&lt;br/&gt;                let patch = super::GitPatch::from_str(&amp;amp;p.content).map_err(|err| {&lt;br/&gt;                    N34Error::InvalidEvent(format!(&lt;br/&gt;                        &amp;#34;Failed to parse the patch `{}`: {err}&amp;#34;,&lt;br/&gt;                        p.id.to_bech32().expect(&amp;#34;Infallible&amp;#34;)&lt;br/&gt;                    ))&lt;br/&gt;                })?;&lt;br/&gt;                N34Result::Ok((patch.filename(&amp;amp;output_path)?, patch))&lt;br/&gt;            })&lt;br/&gt;            .collect::&amp;lt;N34Result&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;&amp;gt;()?;&lt;br/&gt;        patches.push((root_patch.filename(&amp;amp;output_path)?, root_patch));&lt;br/&gt;        patches.sort_unstable_by_key(|p| p.0.clone());&lt;br/&gt;        patches.dedup_by_key(|p| p.0.clone());&lt;br/&gt;&lt;br/&gt;        if output_path.as_path() != Path::new(&amp;#34;&amp;#34;) &amp;amp;&amp;amp; !output_path.exists() {&lt;br/&gt;            fs::create_dir_all(&amp;amp;output_path)?;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        for (patch_path, patch) in patches {&lt;br/&gt;            tracing::info!(&amp;#34;Writeing `{}` in `{}`&amp;#34;, patch.subject, patch_path.display());&lt;br/&gt;            fs::write(patch_path, patch.inner)?;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:50:19&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdx2dugwuue477hlsxc8sl50qdjlz0vu5fqquwedrrmwy9rqkhjyszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrslxap2k</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdx2dugwuue477hlsxc8sl50qdjlz0vu5fqquwedrrmwy9rqkhjyszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrslxap2k" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::PatchStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct DraftArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The open patch id to draft it. Must be orignal root patch&lt;br/&gt;    patch_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for DraftArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::patch_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.patch_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            PatchStatus::Draft,&lt;br/&gt;            None,&lt;br/&gt;            Vec::new(),&lt;br/&gt;            |patch_status| {&lt;br/&gt;                if patch_status.is_drafted() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t draft an already drafted patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_closed() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t draft a closed patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_merged_or_applied() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t draft a merged/applied patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:50:08&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsghjkth0n28fhp8xf3v8arwt7c7l59s4je8wr7mgla582gke7mmpszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsurrr2t</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsghjkth0n28fhp8xf3v8arwt7c7l59s4je8wr7mgla582gke7mmpszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsurrr2t" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::PatchStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct CloseArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The open/drafted patch id to close it. Must be orignal root patch&lt;br/&gt;    patch_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for CloseArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::patch_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.patch_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            PatchStatus::Closed,&lt;br/&gt;            None,&lt;br/&gt;            Vec::new(),&lt;br/&gt;            |patch_status| {&lt;br/&gt;                if patch_status.is_closed() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t close an already closed patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_merged_or_applied() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t close a merged/applied patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:57&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspduwrwernp4zntrpq34u2fm6mkhnn92jwmxtn0zs8a5lcj43d3aszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsalm7er</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspduwrwernp4zntrpq34u2fm6mkhnn92jwmxtn0zs8a5lcj43d3aszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsalm7er" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::hashes::sha1::Hash as Sha1Hash;&lt;br/&gt;&lt;br/&gt;use super::PatchStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, VecNostrEventExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ApplyArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:          Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The open patch id to apply it. Must be orignal root patch or&lt;br/&gt;    /// revision root&lt;br/&gt;    patch_id:        NostrEvent,&lt;br/&gt;    /// Patches that have been applied. Use this when only some patches have&lt;br/&gt;    /// been applied, not all.&lt;br/&gt;    #[arg(long = &amp;#34;patches&amp;#34;, value_name = &amp;#34;PATCH-EVENT-ID&amp;#34;)]&lt;br/&gt;    applied_patches: Vec&amp;lt;NostrEvent&amp;gt;,&lt;br/&gt;    /// The applied commits&lt;br/&gt;    #[arg(num_args = 1..)]&lt;br/&gt;    applied_commits: Vec&amp;lt;Sha1Hash&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ApplyArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::patch_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.patch_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            PatchStatus::MergedApplied,&lt;br/&gt;            Some(either::Either::Right(self.applied_commits)),&lt;br/&gt;            self.applied_patches.into_event_ids(),&lt;br/&gt;            |patch_status| {&lt;br/&gt;                if patch_status.is_merged_or_applied() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t apply an already applied patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_closed() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t apply a closed patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if patch_status.is_drafted() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t apply a drafted patch&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:46&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsvhm8pv6hpgaftj49hfnv2r4pw4fxd6hxm935syrz9fryucrepy0szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszfk2dl</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsvhm8pv6hpgaftj49hfnv2r4pw4fxd6hxm935syrz9fryucrepy0szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszfk2dl" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `config` subcommands&lt;br/&gt;pub mod config;&lt;br/&gt;/// `issue` subcommands&lt;br/&gt;pub mod issue;&lt;br/&gt;/// `patch` subcommands&lt;br/&gt;pub mod patch;&lt;br/&gt;/// `reply` command&lt;br/&gt;pub mod reply;&lt;br/&gt;/// `repo` subcommands&lt;br/&gt;pub mod repo;&lt;br/&gt;/// `sets` subcommands&lt;br/&gt;pub mod sets;&lt;br/&gt;&lt;br/&gt;use std::fmt;&lt;br/&gt;use std::sync::Arc;&lt;br/&gt;use std::time::Duration;&lt;br/&gt;&lt;br/&gt;use clap::{Args, Parser};&lt;br/&gt;use nostr::key::{Keys, SecretKey};&lt;br/&gt;use nostr::nips::nip46::NostrConnectURI;&lt;br/&gt;use nostr::signer::{IntoNostrSigner, NostrSigner};&lt;br/&gt;use nostr_connect::client::NostrConnect;&lt;br/&gt;&lt;br/&gt;use self::config::ConfigSubcommands;&lt;br/&gt;use self::issue::IssueSubcommands;&lt;br/&gt;use self::patch::PatchSubcommands;&lt;br/&gt;use self::reply::ReplyArgs;&lt;br/&gt;use self::repo::RepoSubcommands;&lt;br/&gt;use self::sets::SetsSubcommands;&lt;br/&gt;use super::CliConfig;&lt;br/&gt;use super::options_state::OptionsState;&lt;br/&gt;use super::types::RelayOrSet;&lt;br/&gt;use super::{parsers, traits::CommandRunner};&lt;br/&gt;use crate::cli::Cli;&lt;br/&gt;use crate::cli::types::EchoAuthUrl;&lt;br/&gt;use crate::error::{N34Error, N34Result};&lt;br/&gt;&lt;br/&gt;/// Default path used when no path is provided via command line arguments.&lt;br/&gt;///&lt;br/&gt;/// This is a workaround since Clap doesn&amp;#39;t support lazy evaluation of defaults.&lt;br/&gt;pub const DEFAULT_FALLBACK_PATH: &amp;amp;str = &amp;#34;I_DO_NOT_KNOW_WHY_CLAP_DO_NOT_SUPPORT_LAZY_DEFAULT&amp;#34;;&lt;br/&gt;&lt;br/&gt;/// How long to wait for bunker response (3 minutes).&lt;br/&gt;const BUNKER_TIMEOUT: Duration = Duration::from_secs(60 * 3);&lt;br/&gt;&lt;br/&gt;/// The command-line interface options&lt;br/&gt;#[derive(Args)]&lt;br/&gt;pub struct CliOptions {&lt;br/&gt;    /// Your Nostr secret key&lt;br/&gt;    #[arg(short, long, group = &amp;#34;signer&amp;#34;)]&lt;br/&gt;    pub secret_key: Option&amp;lt;SecretKey&amp;gt;,&lt;br/&gt;    /// NIP-46 bunker url used for signing events&lt;br/&gt;    #[arg(short, long, group = &amp;#34;signer&amp;#34;, value_parser = parsers::parse_bunker_url)]&lt;br/&gt;    pub bunker_url: Option&amp;lt;NostrConnectURI&amp;gt;,&lt;br/&gt;    /// Enables signing events using the browser&amp;#39;s NIP-07 extension. Listens on&lt;br/&gt;    /// `127.0.0.1:51034`.&lt;br/&gt;    #[arg(short = &amp;#39;7&amp;#39;, long, group = &amp;#34;signer&amp;#34;)]&lt;br/&gt;    pub nip07:      bool,&lt;br/&gt;    /// Fallbacks relay to write and read from it. Multiple relays can be&lt;br/&gt;    /// passed.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    pub relays:     Vec&amp;lt;RelayOrSet&amp;gt;,&lt;br/&gt;    /// Proof of Work difficulty when creatring events&lt;br/&gt;    #[arg(long, value_name = &amp;#34;DIFFICULTY&amp;#34;)]&lt;br/&gt;    pub pow:        Option&amp;lt;u8&amp;gt;,&lt;br/&gt;    /// Config path [default: `$XDG_CONFIG_HOME` or `$HOME/.config`]&lt;br/&gt;    #[arg(long, value_name = &amp;#34;PATH&amp;#34;, default_value = DEFAULT_FALLBACK_PATH,&lt;br/&gt;         hide_default_value = true, value_parser = parsers::parse_config_path&lt;br/&gt;     )]&lt;br/&gt;    pub config:     CliConfig,&lt;br/&gt;    /// The state of options. Some values that are used by them but should not&lt;br/&gt;    /// be entered via the CLI&lt;br/&gt;    #[arg(skip)]&lt;br/&gt;    pub state:      OptionsState,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// N34 commands&lt;br/&gt;#[derive(Parser, Debug)]&lt;br/&gt;pub enum Commands {&lt;br/&gt;    /// Manage repositories and relays sets&lt;br/&gt;    Sets {&lt;br/&gt;        #[command(subcommand)]&lt;br/&gt;        subcommands: SetsSubcommands,&lt;br/&gt;    },&lt;br/&gt;    /// Manage repositories&lt;br/&gt;    Repo {&lt;br/&gt;        #[command(subcommand)]&lt;br/&gt;        subcommands: RepoSubcommands,&lt;br/&gt;    },&lt;br/&gt;    /// Manage issues&lt;br/&gt;    Issue {&lt;br/&gt;        #[command(subcommand)]&lt;br/&gt;        subcommands: IssueSubcommands,&lt;br/&gt;    },&lt;br/&gt;    /// Manage patches&lt;br/&gt;    Patch {&lt;br/&gt;        #[command(subcommand)]&lt;br/&gt;        subcommands: PatchSubcommands,&lt;br/&gt;    },&lt;br/&gt;    /// Manage configuration&lt;br/&gt;    Config {&lt;br/&gt;        #[command(subcommand)]&lt;br/&gt;        subcommands: ConfigSubcommands,&lt;br/&gt;    },&lt;br/&gt;    /// Reply to issues and patches.&lt;br/&gt;    Reply(ReplyArgs),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl CliOptions {&lt;br/&gt;    /// Returns the signer&lt;br/&gt;    pub async fn signer(&amp;amp;self) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Arc&amp;lt;dyn NostrSigner &#43; &amp;#39;static&amp;gt;&amp;gt;&amp;gt; {&lt;br/&gt;        if self.nip07 {&lt;br/&gt;            self.state.browser_signer_proxy.start().await?;&lt;br/&gt;&lt;br/&gt;            println!(&lt;br/&gt;                &amp;#34;Browser signer proxy started at: {}&amp;#34;,&lt;br/&gt;                self.state.browser_signer_proxy.url()&lt;br/&gt;            );&lt;br/&gt;&lt;br/&gt;            // FIXME: Use `BrowserSignerProxy::is_session_active` after it release&lt;br/&gt;            // nostr@0.44.0&lt;br/&gt;            tokio::time::sleep(Duration::from_secs(10)).await;&lt;br/&gt;&lt;br/&gt;            return Ok(Some(&lt;br/&gt;                self.state.browser_signer_proxy.clone().into_nostr_signer(),&lt;br/&gt;            ));&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        if let Some(sk) = &amp;amp;self.secret_key {&lt;br/&gt;            return Ok(Some(Keys::new(sk.clone()).into_nostr_signer()));&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        if let Some(ref bunker_url) = self.bunker_url {&lt;br/&gt;            let mut nostrconnect = NostrConnect::new(&lt;br/&gt;                bunker_url.clone(),&lt;br/&gt;                Cli::n34_keypair()?,&lt;br/&gt;                BUNKER_TIMEOUT,&lt;br/&gt;                None,&lt;br/&gt;            )&lt;br/&gt;            .expect(&amp;#34;It&amp;#39;s a bunker url and not a client&amp;#34;);&lt;br/&gt;&lt;br/&gt;            nostrconnect.auth_url_handler(EchoAuthUrl);&lt;br/&gt;            return Ok(Some(nostrconnect.into_nostr_signer()));&lt;br/&gt;        }&lt;br/&gt;        Ok(None)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns an error if there are no relays.&lt;br/&gt;    pub fn ensure_relays(&amp;amp;self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if self.relays.is_empty() {&lt;br/&gt;            return Err(N34Error::EmptyRelays);&lt;br/&gt;        }&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns an error if there are no signers&lt;br/&gt;    pub fn ensure_signer(&amp;amp;self) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if !self.config.keyring_secret_key &amp;amp;&amp;amp; self.secret_key.is_none() &amp;amp;&amp;amp; self.bunker_url.is_none()&lt;br/&gt;        {&lt;br/&gt;            return Err(N34Error::SignerRequired);&lt;br/&gt;        }&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl fmt::Debug for CliOptions {&lt;br/&gt;    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;&amp;#39;_&amp;gt;) -&amp;gt; fmt::Result {&lt;br/&gt;        f.debug_struct(&amp;#34;CliOptions&amp;#34;)&lt;br/&gt;            .field(&amp;#34;secret_key&amp;#34;, &amp;amp;self.secret_key.as_ref().map(|_| &amp;#34;*******&amp;#34;))&lt;br/&gt;            .field(&amp;#34;bunker_url&amp;#34;, &amp;amp;self.bunker_url.as_ref().map(|_| &amp;#34;*******&amp;#34;))&lt;br/&gt;            .field(&amp;#34;relays&amp;#34;, &amp;amp;self.relays)&lt;br/&gt;            .field(&amp;#34;pow&amp;#34;, &amp;amp;self.pow)&lt;br/&gt;            .field(&amp;#34;config&amp;#34;, &amp;amp;self.config)&lt;br/&gt;            .finish()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for Commands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        tracing::trace!(&amp;#34;Options: {options:#?}&amp;#34;);&lt;br/&gt;        tracing::trace!(&amp;#34;Handling: {self:#?}&amp;#34;);&lt;br/&gt;&lt;br/&gt;        crate::run_command!(self, options, Repo Issue Sets Patch Config &amp;amp; Reply)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:34&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsp6l2flpzdxq90x5tva07c2lswvph43jmmt4dtye9ddfnqsp7wkkqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs3alula</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsp6l2flpzdxq90x5tva07c2lswvph43jmmt4dtye9ddfnqsp7wkkqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs3alula" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::{event::Kind, filter::Filter};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;    nostr_utils::{&lt;br/&gt;        NostrClient,&lt;br/&gt;        traits::{GitIssueUtils, NaddrsUtils, ReposUtils},&lt;br/&gt;        utils,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ViewArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The issue id to view it&lt;br/&gt;    issue_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ViewArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = utils::naddrs_or_file(&lt;br/&gt;            self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;            &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;        )?;&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;&lt;br/&gt;        client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;        client.add_relays(&amp;amp;self.issue_id.relays).await;&lt;br/&gt;        client&lt;br/&gt;            .add_relays(&lt;br/&gt;                &amp;amp;client&lt;br/&gt;                    .fetch_repos(&amp;amp;naddrs.into_coordinates())&lt;br/&gt;                    .await?&lt;br/&gt;                    .extract_relays(),&lt;br/&gt;            )&lt;br/&gt;            .await;&lt;br/&gt;&lt;br/&gt;        let issue = client&lt;br/&gt;            .fetch_event(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .id(self.issue_id.event_id)&lt;br/&gt;                    .kind(Kind::GitIssue),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .ok_or(N34Error::CanNotFoundIssue)?;&lt;br/&gt;&lt;br/&gt;        let issue_subject = utils::smart_wrap(issue.extract_issue_subject(), 70);&lt;br/&gt;        let issue_author = client.get_username(issue.pubkey).await;&lt;br/&gt;        let mut issue_labels = utils::smart_wrap(&amp;amp;issue.extract_issue_labels(), 70);&lt;br/&gt;&lt;br/&gt;        if issue_labels.is_empty() {&lt;br/&gt;            issue_labels = &amp;#34;\n&amp;#34;.to_owned();&lt;br/&gt;        } else {&lt;br/&gt;            issue_labels = format!(&amp;#34;{issue_labels}\n\n&amp;#34;)&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        println!(&lt;br/&gt;            &amp;#34;{issue_subject} - [by {issue_author}]\n{issue_labels}{}&amp;#34;,&lt;br/&gt;            utils::smart_wrap(&amp;amp;issue.content, 80)&lt;br/&gt;        );&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:23&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsz6x8gqkm4ej9pw8uxwfcqpfdkqyf0szufgdzj3ckar08n65qfwvqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrse3q5at</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsz6x8gqkm4ej9pw8uxwfcqpfdkqyf0szufgdzj3ckar08n65qfwvqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrse3q5at" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::IssueStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ResolveArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The issue id to resolve it&lt;br/&gt;    issue_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ResolveArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::issue_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.issue_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            IssueStatus::Resolved,&lt;br/&gt;            |issue_status| {&lt;br/&gt;                if issue_status.is_resolved() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t resolve an resolved issue&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:12&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8wv9e6tm43cxqwjrg603zymcky38ncu7aa2j4v0ehkf7tf7gue4qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfndh6g</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8wv9e6tm43cxqwjrg603zymcky38ncu7aa2j4v0ehkf7tf7gue4qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfndh6g" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::IssueStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ReopenArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The closed issue id to reopen it&lt;br/&gt;    issue_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ReopenArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::issue_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.issue_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            IssueStatus::Open,&lt;br/&gt;            |issue_status| {&lt;br/&gt;                if issue_status.is_open() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t reopen an open issue&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if issue_status.is_resolved() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t open a resolved issue&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:49:02&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsr79wnqtqlvdqxluhs4m37z6xgkh0duk32ygrkfjer5qx208huusgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsq79ldc</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsr79wnqtqlvdqxluhs4m37z6xgkh0duk32ygrkfjer5qx208huusgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsq79ldc" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;use clap::{ArgGroup, Args};&lt;br/&gt;use nostr::event::{EventBuilder, Tag};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        CommandRunner,&lt;br/&gt;        traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt},&lt;br/&gt;        types::NaddrOrSet,&lt;br/&gt;    },&lt;br/&gt;    error::N34Result,&lt;br/&gt;    nostr_utils::{&lt;br/&gt;        NostrClient,&lt;br/&gt;        traits::{NaddrsUtils, NewGitRepositoryAnnouncement, ReposUtils},&lt;br/&gt;        utils,&lt;br/&gt;    },&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Arguments for the `issue new` command&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;#[clap(&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;issue-content&amp;#34;)&lt;br/&gt;            .args([&amp;#34;content&amp;#34;, &amp;#34;editor&amp;#34;])&lt;br/&gt;            .required(true)&lt;br/&gt;    ),&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;issue-subject&amp;#34;)&lt;br/&gt;            .args([&amp;#34;editor&amp;#34;, &amp;#34;subject&amp;#34;])&lt;br/&gt;    )&lt;br/&gt;)]&lt;br/&gt;pub struct NewArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:  Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// Markdown content for the issue. Cannot be used together with the&lt;br/&gt;    /// `--editor` flag.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    content: Option&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Opens the user&amp;#39;s default editor to write issue content. The first line&lt;br/&gt;    /// will be used as the issue subject.&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    editor:  bool,&lt;br/&gt;    /// The issue subject. Cannot be used together with the `--editor` flag.&lt;br/&gt;    #[arg(long)]&lt;br/&gt;    subject: Option&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Labels for the issue. Can be specified as arguments (-l bug) or hashtags&lt;br/&gt;    /// in content (#bug).&lt;br/&gt;    #[arg(short, long)]&lt;br/&gt;    label:   Vec&amp;lt;String&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl NewArgs {&lt;br/&gt;    /// Returns the subject and the content of the issue. (subject, content)&lt;br/&gt;    pub fn issue_content(&amp;amp;self) -&amp;gt; N34Result&amp;lt;(Option&amp;lt;String&amp;gt;, String)&amp;gt; {&lt;br/&gt;        if let Some(content) = self.content.as_ref() {&lt;br/&gt;            if let Some(subject) = self.subject.as_ref() {&lt;br/&gt;                return Ok((Some(subject.trim().to_owned()), content.trim().to_owned()));&lt;br/&gt;            }&lt;br/&gt;            return Ok((None, content.trim().to_owned()));&lt;br/&gt;        }&lt;br/&gt;        // If the `self.content` is `None` then the `self.editor` is `true`&lt;br/&gt;        let file_content = utils::read_editor(None, &amp;#34;.md&amp;#34;)?;&lt;br/&gt;        if file_content.contains(&amp;#39;\n&amp;#39;) {&lt;br/&gt;            Ok(file_content&lt;br/&gt;                .split_once(&amp;#39;\n&amp;#39;)&lt;br/&gt;                .map(|(s, c)| (Some(s.trim().to_owned()), c.trim().to_owned()))&lt;br/&gt;                .expect(&amp;#34;There is a new line&amp;#34;))&lt;br/&gt;        } else {&lt;br/&gt;            tracing::info!(&amp;#34;File content contains only issue body without a subject line&amp;#34;);&lt;br/&gt;            Ok((None, file_content))&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for NewArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(&lt;br/&gt;            self.naddrs.flat_naddrs(&amp;amp;options.config.sets)?,&lt;br/&gt;            &amp;amp;utils::nostr_address_path()?,&lt;br/&gt;        )?)?;&lt;br/&gt;        let relays = options.relays.clone().flat_relays(&amp;amp;options.config.sets)?;&lt;br/&gt;        let client = NostrClient::init(&amp;amp;options, &amp;amp;relays).await;&lt;br/&gt;        let user_pubk = client.pubkey().await?;&lt;br/&gt;        let coordinates = naddrs.clone().into_coordinates();&lt;br/&gt;        client.add_relays(&amp;amp;naddrs.extract_relays()).await;&lt;br/&gt;        let repos = client.fetch_repos(coordinates.as_slice()).await?;&lt;br/&gt;        let maintainers = repos.extract_maintainers();&lt;br/&gt;        client.add_relays(&amp;amp;repos.extract_relays()).await;&lt;br/&gt;        let relays_list = client.user_relays_list(user_pubk).await?;&lt;br/&gt;        client&lt;br/&gt;            .add_relays(&amp;amp;utils::add_read_relays(relays_list.as_ref()))&lt;br/&gt;            .await;&lt;br/&gt;&lt;br/&gt;        let (subject, content) = self.issue_content()?;&lt;br/&gt;        let content_details = client.parse_content(&amp;amp;content).await;&lt;br/&gt;&lt;br/&gt;        let event =&lt;br/&gt;            EventBuilder::new_git_issue(coordinates.as_slice(), content, subject, self.label)?&lt;br/&gt;                .dedup_tags()&lt;br/&gt;                .pow(options.pow.unwrap_or_default())&lt;br/&gt;                .tags(maintainers.iter().map(|p| Tag::public_key(*p)))&lt;br/&gt;                .tags(content_details.clone().into_tags())&lt;br/&gt;                .build(user_pubk);&lt;br/&gt;        let event_id = event.id.expect(&amp;#34;There is an id&amp;#34;);&lt;br/&gt;&lt;br/&gt;        let write_relays = [&lt;br/&gt;            relays,&lt;br/&gt;            naddrs.extract_relays(),&lt;br/&gt;            utils::add_write_relays(relays_list.as_ref()),&lt;br/&gt;            client&lt;br/&gt;                .fetch_repos(&amp;amp;naddrs.into_coordinates())&lt;br/&gt;                .await?&lt;br/&gt;                .extract_relays(),&lt;br/&gt;            // Include read relays for each maintainer (if found)&lt;br/&gt;            client.read_relays_from_users(&amp;amp;maintainers).await,&lt;br/&gt;            content_details.write_relays.clone().into_iter().collect(),&lt;br/&gt;        ]&lt;br/&gt;        .concat();&lt;br/&gt;&lt;br/&gt;        tracing::trace!(relays = ?write_relays, &amp;#34;Write relays list&amp;#34;);&lt;br/&gt;        let success = client&lt;br/&gt;            .send_event_to(event, relays_list.as_ref(), &amp;amp;write_relays)&lt;br/&gt;            .await?;&lt;br/&gt;&lt;br/&gt;        let nevent = utils::new_nevent(event_id, &amp;amp;success)?;&lt;br/&gt;        println!(&amp;#34;Issue created: {nevent}&amp;#34;);&lt;br/&gt;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:48:50&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2lsxvefdfclfgr458tfcshh3eusnqk9c0gqc0k79ewrc6nmhpdwgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6277hp</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2lsxvefdfclfgr458tfcshh3eusnqk9c0gqc0k79ewrc6nmhpdwgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6277hp" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `issue close` subcommand&lt;br/&gt;mod close;&lt;br/&gt;/// `issue list` subcommand&lt;br/&gt;mod list;&lt;br/&gt;/// `issue new` subcommand&lt;br/&gt;mod new;&lt;br/&gt;/// `issue reopen` subcommand&lt;br/&gt;mod reopen;&lt;br/&gt;/// `issue resolve` subcommand&lt;br/&gt;mod resolve;&lt;br/&gt;/// `issue view` subcommand&lt;br/&gt;mod view;&lt;br/&gt;&lt;br/&gt;use std::fmt;&lt;br/&gt;&lt;br/&gt;use clap::Subcommand;&lt;br/&gt;use nostr::event::Kind;&lt;br/&gt;&lt;br/&gt;use self::close::CloseArgs;&lt;br/&gt;use self::list::ListArgs;&lt;br/&gt;use self::new::NewArgs;&lt;br/&gt;use self::reopen::ReopenArgs;&lt;br/&gt;use self::resolve::ResolveArgs;&lt;br/&gt;use self::view::ViewArgs;&lt;br/&gt;use super::{CliOptions, CommandRunner};&lt;br/&gt;use crate::error::{N34Error, N34Result};&lt;br/&gt;&lt;br/&gt;/// Prefix used for git issue alt.&lt;br/&gt;pub const ISSUE_ALT_PREFIX: &amp;amp;str = &amp;#34;git issue: &amp;#34;;&lt;br/&gt;&lt;br/&gt;#[derive(Subcommand, Debug)]&lt;br/&gt;pub enum IssueSubcommands {&lt;br/&gt;    /// Create a new repository issue&lt;br/&gt;    New(NewArgs),&lt;br/&gt;    /// View an issue by its ID&lt;br/&gt;    View(ViewArgs),&lt;br/&gt;    /// Reopens a closed issue.&lt;br/&gt;    Reopen(ReopenArgs),&lt;br/&gt;    /// Closes an open issue.&lt;br/&gt;    Close(CloseArgs),&lt;br/&gt;    /// Resolves an issue.&lt;br/&gt;    Resolve(ResolveArgs),&lt;br/&gt;    /// List the repositories issues.&lt;br/&gt;    List(ListArgs),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Possible states for a Git issue&lt;br/&gt;#[derive(Debug)]&lt;br/&gt;pub enum IssueStatus {&lt;br/&gt;    /// The issue is currently open&lt;br/&gt;    Open,&lt;br/&gt;    /// The issue has been resolved&lt;br/&gt;    Resolved,&lt;br/&gt;    /// The issue has been closed&lt;br/&gt;    Closed,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl IssueStatus {&lt;br/&gt;    /// Maps the issue status to its corresponding Nostr kind.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn kind(&amp;amp;self) -&amp;gt; Kind {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Open =&amp;gt; Kind::GitStatusOpen,&lt;br/&gt;            Self::Resolved =&amp;gt; Kind::GitStatusApplied,&lt;br/&gt;            Self::Closed =&amp;gt; Kind::GitStatusClosed,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the string representation of the issue status.&lt;br/&gt;    pub const fn as_str(&amp;amp;self) -&amp;gt; &amp;amp;&amp;#39;static str {&lt;br/&gt;        match self {&lt;br/&gt;            Self::Open =&amp;gt; &amp;#34;Open&amp;#34;,&lt;br/&gt;            Self::Resolved =&amp;gt; &amp;#34;Resolved&amp;#34;,&lt;br/&gt;            Self::Closed =&amp;gt; &amp;#34;Closed&amp;#34;,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the issue is open.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_open(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Open)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the issue is resolved.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_resolved(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Resolved)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Check if the issue is closed.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_closed(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, Self::Closed)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl From&amp;lt;&amp;amp;IssueStatus&amp;gt; for Kind {&lt;br/&gt;    fn from(status: &amp;amp;IssueStatus) -&amp;gt; Self {&lt;br/&gt;        status.kind()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl fmt::Display for IssueStatus {&lt;br/&gt;    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;&amp;#39;_&amp;gt;) -&amp;gt; fmt::Result {&lt;br/&gt;        write!(f, &amp;#34;{}&amp;#34;, self.as_str())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl TryFrom&amp;lt;Kind&amp;gt; for IssueStatus {&lt;br/&gt;    type Error = N34Error;&lt;br/&gt;&lt;br/&gt;    fn try_from(kind: Kind) -&amp;gt; Result&amp;lt;Self, Self::Error&amp;gt; {&lt;br/&gt;        match kind {&lt;br/&gt;            Kind::GitStatusOpen =&amp;gt; Ok(Self::Open),&lt;br/&gt;            Kind::GitStatusApplied =&amp;gt; Ok(Self::Resolved),&lt;br/&gt;            Kind::GitStatusClosed =&amp;gt; Ok(Self::Closed),&lt;br/&gt;            _ =&amp;gt; Err(N34Error::InvalidIssueStatus(kind)),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for IssueSubcommands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::run_command!(self, options, &amp;amp; New View Reopen Close Resolve List)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:48:39&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs24a39v2zltufgkwtkdh7t4hu3z5km7khyq2ec4qheucgaft0wfdgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxcld54</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs24a39v2zltufgkwtkdh7t4hu3z5km7khyq2ec4qheucgaft0wfdgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxcld54" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::num::NonZeroUsize;&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, common_commands, traits::CommandRunner, types::NaddrOrSet},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct ListArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;)]&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// Maximum number of issues to list&lt;br/&gt;    #[arg(long, default_value = &amp;#34;15&amp;#34;)]&lt;br/&gt;    limit:  NonZeroUsize,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ListArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        common_commands::list_patches_and_issues(options, self.naddrs, false, self.limit.into())&lt;br/&gt;            .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:48:28&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxrhanemyn5h2ryz7vt0ztrw8h6shxfzgnuu0a86akyrdzdhtkk6czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2nzqcc</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxrhanemyn5h2ryz7vt0ztrw8h6shxfzgnuu0a86akyrdzdhtkk6czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2nzqcc" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use super::IssueStatus;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{&lt;br/&gt;        CliOptions,&lt;br/&gt;        traits::CommandRunner,&lt;br/&gt;        types::{NaddrOrSet, NostrEvent},&lt;br/&gt;    },&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Debug, Args)]&lt;br/&gt;pub struct CloseArgs {&lt;br/&gt;    /// Repository address in `naddr` format (`naddr1...`), NIP-05 format&lt;br/&gt;    /// (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`.&lt;br/&gt;    ///&lt;br/&gt;    /// If omitted, looks for a `nostr-address` file.&lt;br/&gt;    #[arg(value_name = &amp;#34;NADDR-NIP05-OR-SET&amp;#34;, long = &amp;#34;repo&amp;#34;)]&lt;br/&gt;    naddrs:   Option&amp;lt;Vec&amp;lt;NaddrOrSet&amp;gt;&amp;gt;,&lt;br/&gt;    /// The open issue id to close it&lt;br/&gt;    issue_id: NostrEvent,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for CloseArgs {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::cli::common_commands::issue_status_command(&lt;br/&gt;            options,&lt;br/&gt;            self.issue_id,&lt;br/&gt;            self.naddrs,&lt;br/&gt;            IssueStatus::Closed,&lt;br/&gt;            |issue_status| {&lt;br/&gt;                if issue_status.is_closed() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t close an already closed issue&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;&lt;br/&gt;                if issue_status.is_resolved() {&lt;br/&gt;                    return Err(N34Error::InvalidStatus(&lt;br/&gt;                        &amp;#34;You can&amp;#39;t close a resolved issue&amp;#34;.to_owned(),&lt;br/&gt;                    ));&lt;br/&gt;                }&lt;br/&gt;                Ok(())&lt;br/&gt;            },&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:48:17&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsv2mus36xh43s53673v646vj54f4pz862l2zrs4j6ycrvjjvz7mrqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsce564m</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsv2mus36xh43s53673v646vj54f4pz862l2zrs4j6ycrvjjvz7mrqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsce564m" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::types::RelayUrl;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, traits::CommandRunner},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct RelaysArgs {&lt;br/&gt;    /// List of relay URLs to append to fallback relays. If empty, removes all&lt;br/&gt;    /// fallback relays.&lt;br/&gt;    relays:          Vec&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;    /// Replace existing fallback relays instead of appending new ones.&lt;br/&gt;    #[arg(long = &amp;#34;override&amp;#34;)]&lt;br/&gt;    override_relays: bool,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for RelaysArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if self.relays.is_empty() {&lt;br/&gt;            options.config.fallback_relays = None;&lt;br/&gt;        } else if self.override_relays {&lt;br/&gt;            options.config.fallback_relays = Some(self.relays);&lt;br/&gt;        } else {&lt;br/&gt;            let mut relays = options.config.fallback_relays.clone().unwrap_or_default();&lt;br/&gt;            relays.extend(self.relays);&lt;br/&gt;            relays.sort_unstable();&lt;br/&gt;            relays.dedup();&lt;br/&gt;            options.config.fallback_relays = Some(relays);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:48:06&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2cezqwzwgvdyl00klm5g4z0nru628n6utlztsc88vnvwk6vh7arczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrswv2cy5</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2cezqwzwgvdyl00klm5g4z0nru628n6utlztsc88vnvwk6vh7arczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrswv2cy5" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, traits::CommandRunner},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct PowArgs {&lt;br/&gt;    /// The new default PoW difficulty&lt;br/&gt;    difficulty: u8,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for PowArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        options.config.pow = Some(self.difficulty);&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:55&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgg74j5a33ychtfres7xl05q846jmmmychyechrnhsdnlmeeqcnugzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0q3mq5</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgg74j5a33ychtfres7xl05q846jmmmychyechrnhsdnlmeeqcnugzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0q3mq5" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::net::SocketAddr;&lt;br/&gt;&lt;br/&gt;use clap::{ArgGroup, Args};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, options_state::DEFAULT_NIP07_PROXY_ADDR, traits::CommandRunner},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;#[clap(&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;options&amp;#34;)&lt;br/&gt;            .required(true)&lt;br/&gt;    )&lt;br/&gt;)]&lt;br/&gt;pub struct Nip07Args {&lt;br/&gt;    /// Enable NIP-07 as the default signer.&lt;br/&gt;    #[arg(long, group = &amp;#34;options&amp;#34;)]&lt;br/&gt;    enable:  bool,&lt;br/&gt;    /// Disable NIP-07 as the default signer.&lt;br/&gt;    #[arg(long, group = &amp;#34;options&amp;#34;, group = &amp;#34;disable_options&amp;#34;)]&lt;br/&gt;    disable: bool,&lt;br/&gt;    /// Set the default `ip:port` for the browser signer proxy.&lt;br/&gt;    #[arg(long, group = &amp;#34;disable_options&amp;#34;)]&lt;br/&gt;    addr:    Option&amp;lt;SocketAddr&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for Nip07Args {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if self.enable {&lt;br/&gt;            let addr = self.addr.unwrap_or(DEFAULT_NIP07_PROXY_ADDR);&lt;br/&gt;            options.config.nip07 = Some(addr)&lt;br/&gt;        } else {&lt;br/&gt;            options.config.nip07 = None&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:45&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsy2lx7qx293763gk4q4qczr5n56f9n35m535md0ampk7mcx5r0wlqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfp6pkp</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsy2lx7qx293763gk4q4qczr5n56f9n35m535md0ampk7mcx5r0wlqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfp6pkp" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// `config bunker` subcommand&lt;br/&gt;mod bunker;&lt;br/&gt;/// `config keyring` subcommand&lt;br/&gt;mod keyring;&lt;br/&gt;/// `config nip07` subcommand&lt;br/&gt;mod nip07;&lt;br/&gt;/// `config pow` subcommand&lt;br/&gt;mod pow;&lt;br/&gt;/// `config relays` subcommand&lt;br/&gt;mod relays;&lt;br/&gt;&lt;br/&gt;use clap::Subcommand;&lt;br/&gt;&lt;br/&gt;use self::bunker::BunkerArgs;&lt;br/&gt;use self::keyring::KeyringArgs;&lt;br/&gt;use self::nip07::Nip07Args;&lt;br/&gt;use self::pow::PowArgs;&lt;br/&gt;use self::relays::RelaysArgs;&lt;br/&gt;use super::CliOptions;&lt;br/&gt;use crate::{cli::traits::CommandRunner, error::N34Result};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;#[derive(Subcommand, Debug)]&lt;br/&gt;pub enum ConfigSubcommands {&lt;br/&gt;    /// Sets the default PoW difficulty (0 if not specified)&lt;br/&gt;    Pow(PowArgs),&lt;br/&gt;    /// Sets the default fallback relays if none provided. Use this relays for&lt;br/&gt;    /// read and write.&lt;br/&gt;    Relays(RelaysArgs),&lt;br/&gt;    /// Sets a URL of NIP-46 bunker server used for signing events.&lt;br/&gt;    Bunker(BunkerArgs),&lt;br/&gt;    /// Managing the secret key keyring, including enabling, disabling, or&lt;br/&gt;    /// resetting it.&lt;br/&gt;    Keyring(KeyringArgs),&lt;br/&gt;    /// Controls the NIP-07 browser signer proxy, turning it on or off, and&lt;br/&gt;    /// configures the `ip:port` address.&lt;br/&gt;    Nip07(Nip07Args),&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for ConfigSubcommands {&lt;br/&gt;    async fn run(self, options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        crate::run_command!(self, options, &amp;amp; Pow Relays Bunker Keyring Nip07)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:34&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs0gs57s2p74nthetl6r356unpqdp0t5kx09e0jl8aarry6j8adz8szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6kw7v8</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs0gs57s2p74nthetl6r356unpqdp0t5kx09e0jl8aarry6j8adz8szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6kw7v8" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::{ArgGroup, Args};&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{Cli, CliOptions, traits::CommandRunner},&lt;br/&gt;    error::N34Result,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;#[clap(&lt;br/&gt;    group(&lt;br/&gt;        ArgGroup::new(&amp;#34;options&amp;#34;)&lt;br/&gt;            .required(true)&lt;br/&gt;    )&lt;br/&gt;)]&lt;br/&gt;pub struct KeyringArgs {&lt;br/&gt;    /// Turns on secret key keyring. Requires entering the key once when&lt;br/&gt;    /// enabled.&lt;br/&gt;    #[arg(long, group = &amp;#34;options&amp;#34;)]&lt;br/&gt;    enable:  bool,&lt;br/&gt;    /// Turns off secret key keyring. Removes any existing key and prevents&lt;br/&gt;    /// storing new ones.&lt;br/&gt;    #[arg(long, group = &amp;#34;options&amp;#34;)]&lt;br/&gt;    disable: bool,&lt;br/&gt;    /// Deletes current key and stores the next provided key.&lt;br/&gt;    #[arg(long, group = &amp;#34;options&amp;#34;)]&lt;br/&gt;    reset:   bool,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl CommandRunner for KeyringArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        let keyring = nostr_keyring::NostrKeyring::new(Cli::N34_KEYRING_SERVICE_NAME);&lt;br/&gt;&lt;br/&gt;        if self.enable {&lt;br/&gt;            options.config.keyring_secret_key = true;&lt;br/&gt;        } else if self.disable {&lt;br/&gt;            options.config.keyring_secret_key = false;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        if self.reset || self.disable {&lt;br/&gt;            let _ = keyring.delete(Cli::USER_KEY_PAIR_ENTRY);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:23&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfqkg5cqk923e8ysegwcv46nxmhlkna48z88v68yya22s7eyxtv0gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4vpdvw</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfqkg5cqk923e8ysegwcv46nxmhlkna48z88v68yya22s7eyxtv0gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4vpdvw" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use clap::Args;&lt;br/&gt;use nostr::nips::nip46::NostrConnectURI;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, traits::CommandRunner},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;#[derive(Args, Debug)]&lt;br/&gt;pub struct BunkerArgs {&lt;br/&gt;    /// Nostr Connect URL for the bunker. Omit this to remove the current bunker&lt;br/&gt;    /// URL.&lt;br/&gt;    bunker_url: Option&amp;lt;NostrConnectURI&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;impl CommandRunner for BunkerArgs {&lt;br/&gt;    const NEED_SIGNER: bool = false;&lt;br/&gt;&lt;br/&gt;    async fn run(self, mut options: CliOptions) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        if let Some(ref bunker_url) = self.bunker_url&lt;br/&gt;            &amp;amp;&amp;amp; !bunker_url.is_bunker()&lt;br/&gt;        {&lt;br/&gt;            return Err(N34Error::NotBunkerUrl);&lt;br/&gt;        }&lt;br/&gt;        options.config.bunker_url = self.bunker_url;&lt;br/&gt;        options.config.dump()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:12&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstrkxzssd77ykz55zpz5lrdxlk09hds2ya5n29v9egt55ujjgf04gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxqhzz4</id>
    
      <title type="html">#!/usr/bin/env sh cargo run --bin n34 -- repo view ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstrkxzssd77ykz55zpz5lrdxlk09hds2ya5n29v9egt55ujjgf04gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsxqhzz4" />
    <content type="html">
      #!/usr/bin/env sh&lt;br/&gt;cargo run --bin n34 -- repo view naddr1qqpkuve5qgsqqqqqq9g9uljgjfcyd6dm4fegk8em2yfz0c3qp3tc6mntkrrhawgrqsqqqaueqyf8wumn8ghj7mn0wd68yt35wfejumnvqyt8wumn8ghj7un9d3shjtnswf5k6ctv9ehx2aqpp4mhxue69uhkummn9ekx7mqppamhxue69uhkummnw3ezumt0d5q3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qg5waehxw309ahx7um5wghx77r5wghxgetkxpx8xj&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:47:02&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspaxnxxnw7geqmae06xnlxs6etk93vcwcf9jtxxf9ajytx0945txqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsmav4xs</id>
    
      <title type="html">unstable_features = true style_edition = &amp;#34;2024&amp;#34; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspaxnxxnw7geqmae06xnlxs6etk93vcwcf9jtxxf9ajytx0945txqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsmav4xs" />
    <content type="html">
      unstable_features = true&lt;br/&gt;style_edition     = &amp;#34;2024&amp;#34;&lt;br/&gt;&lt;br/&gt;blank_lines_upper_bound        = 2&lt;br/&gt;combine_control_expr           = false&lt;br/&gt;wrap_comments                  = true&lt;br/&gt;condense_wildcard_suffixes     = true&lt;br/&gt;edition                        = &amp;#34;2024&amp;#34;&lt;br/&gt;enum_discrim_align_threshold   = 20&lt;br/&gt;force_multiline_blocks         = true&lt;br/&gt;format_code_in_doc_comments    = true&lt;br/&gt;format_generated_files         = false&lt;br/&gt;format_macro_matchers          = true&lt;br/&gt;format_strings                 = true&lt;br/&gt;imports_layout                 = &amp;#34;HorizontalVertical&amp;#34;&lt;br/&gt;newline_style                  = &amp;#34;Unix&amp;#34;&lt;br/&gt;normalize_comments             = true&lt;br/&gt;reorder_impl_items             = true&lt;br/&gt;group_imports                  = &amp;#34;StdExternalCrate&amp;#34;&lt;br/&gt;single_line_let_else_max_width = 0&lt;br/&gt;struct_field_align_threshold   = 20&lt;br/&gt;use_try_shorthand              = true&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:46:51&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstl9kppgua3q7z7ecxlxj9thkrmt78f6yaa3vas25l5fxvdznczygzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0u9l05</id>
    
      <title type="html"># This file contains NIP-19 `naddr` entities for repositories ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstl9kppgua3q7z7ecxlxj9thkrmt78f6yaa3vas25l5fxvdznczygzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0u9l05" />
    <content type="html">
      # This file contains NIP-19 `naddr` entities for repositories that accept this&lt;br/&gt;# project&amp;#39;s issues and patches.&lt;br/&gt;#&lt;br/&gt;# The file acts as a **read-only reference** for retrieving repository relays&lt;br/&gt;# when embedded in an `naddr` and mentions those repositories when opening&lt;br/&gt;# patches or issues. Modifications here will not affect in the relays, as the&lt;br/&gt;# file is **explicitly untracked**. Its goal is to simplify contributions by&lt;br/&gt;# removing the need for manual address entry.&lt;br/&gt;#&lt;br/&gt;# Each entry must start with &amp;#34;naddr&amp;#34;. Embedded relays are **strongly recommended**&lt;br/&gt;# to assist client-side discovery.&lt;br/&gt;#&lt;br/&gt;# Empty lines are ignored. Lines starting with &amp;#34;#&amp;#34; are treated as comments.&lt;br/&gt;&lt;br/&gt;naddr1qqpkuve5qgsqqqqqq9g9uljgjfcyd6dm4fegk8em2yfz0c3qp3tc6mntkrrhawgrqsqqqaueqyf8wumn8ghj7mn0wd68yt35wfejumnvqyt8wumn8ghj7un9d3shjtnswf5k6ctv9ehx2aqpp4mhxue69uhkummn9ekx7mqppamhxue69uhkummnw3ezumt0d5q3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qg5waehxw309ahx7um5wghx77r5wghxgetkxpx8xj&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:46:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstt8q7uk0g78e8p2yrlhesl5gpymdq8y46v6g4url7xhrth8nrlagzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrse98673</id>
    
      <title type="html"># This justfile is for the contrbutors of this project, not for ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstt8q7uk0g78e8p2yrlhesl5gpymdq8y46v6g4url7xhrth8nrlagzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrse98673" />
    <content type="html">
      # This justfile is for the contrbutors of this project, not for the end user.&lt;br/&gt;#&lt;br/&gt;# Requirements for this justfile:&lt;br/&gt;# - Linux distribution&lt;br/&gt;# - just (Of course) &amp;lt;&lt;a href=&#34;https://github.com/casey/just&amp;gt&#34;&gt;https://github.com/casey/just&amp;gt&lt;/a&gt;;&lt;br/&gt;# - cargo (For the build and tests) &amp;lt;&lt;a href=&#34;https://doc.rust-lang.org/cargo/getting-started/installation.html&amp;gt&#34;&gt;https://doc.rust-lang.org/cargo/getting-started/installation.html&amp;gt&lt;/a&gt;;&lt;br/&gt;# - mdbook (&amp;lt;&lt;a href=&#34;https://rust-lang.github.io/mdBook&amp;gt&#34;&gt;https://rust-lang.github.io/mdBook&amp;gt&lt;/a&gt;;)&lt;br/&gt;# - git-cliff (&amp;lt;&lt;a href=&#34;https://git-cliff.org&amp;gt&#34;&gt;https://git-cliff.org&amp;gt&lt;/a&gt;;)&lt;br/&gt;# - taplo (&amp;lt;&lt;a href=&#34;https://taplo.tamasfe.dev/&amp;gt&#34;&gt;https://taplo.tamasfe.dev/&amp;gt&lt;/a&gt;;)&lt;br/&gt;# - cargo-msrv (&amp;lt;&lt;a href=&#34;https://github.com/foresterre/cargo-msrv&amp;gt&#34;&gt;https://github.com/foresterre/cargo-msrv&amp;gt&lt;/a&gt;;)&lt;br/&gt;# - nushell (&amp;lt;&lt;a href=&#34;https://nushell.sh&amp;gt&#34;&gt;https://nushell.sh&amp;gt&lt;/a&gt;;)&lt;br/&gt;&lt;br/&gt;set quiet&lt;br/&gt;set unstable&lt;br/&gt;set shell := [&amp;#34;/usr/bin/env&amp;#34;, &amp;#34;bash&amp;#34;, &amp;#34;-c&amp;#34;]&lt;br/&gt;set script-interpreter := [&amp;#34;/usr/bin/env&amp;#34;, &amp;#34;nu&amp;#34;]&lt;br/&gt;&lt;br/&gt;JUST_EXECUTABLE := &amp;#34;just -u -f &amp;#34; &#43; justfile()&lt;br/&gt;header := &amp;#34;Available tasks:\n&amp;#34;&lt;br/&gt;BOOK_DEST_DIR := &amp;#34;dest&amp;#34;&lt;br/&gt;tag_change_body := &amp;#39;&amp;#39;&amp;#39;{% for group, commits in commits | group_by(attribute=&amp;#34;group&amp;#34;) %}&lt;br/&gt;&lt;br/&gt;{{ group | upper_first }}&lt;br/&gt;&lt;br/&gt;{% for commit in commits %}&lt;br/&gt;- {{ commit.message | split(pat=&amp;#34;\n&amp;#34;) | first | split(pat=&amp;#34;:&amp;#34;) | slice(start=1) | join(sep=&amp;#34;:&amp;#34;) | upper_first | trim }} - by {{ commit.author.name}}{% endfor %}{% endfor %}&lt;br/&gt;&amp;#39;&amp;#39;&amp;#39;&lt;br/&gt;&lt;br/&gt;export TZ := &amp;#34;UTC&amp;#34;&lt;br/&gt;&lt;br/&gt;_default:&lt;br/&gt;    @{{JUST_EXECUTABLE}} --list-heading &amp;#34;{{header}}&amp;#34; --list&lt;br/&gt;&lt;br/&gt;# Run the CI&lt;br/&gt;ci: &amp;amp;&amp;amp; msrv _done_ci&lt;br/&gt;    echo &amp;#34;🔨 Building n34...&amp;#34;&lt;br/&gt;    cargo build -q&lt;br/&gt;    echo &amp;#34;🔍 Checking code formatting...&amp;#34;&lt;br/&gt;    cargo fmt -q -- --check&lt;br/&gt;    RUST_LOG=none taplo fmt --check --config &amp;#34;./.taplo.toml&amp;#34; || (echo &amp;#34;❌Toml files is not properly formatted&amp;#34; &amp;amp;&amp;amp; exit 1)&lt;br/&gt;    echo &amp;#34;🧹 Running linter checks...&amp;#34;&lt;br/&gt;    cargo clippy -q -- -D warnings&lt;br/&gt;    echo &amp;#34;🧪 Running tests...&amp;#34;&lt;br/&gt;    cargo test -q&lt;br/&gt;&lt;br/&gt;# Check that the current MSRV is correct&lt;br/&gt;msrv:&lt;br/&gt;    echo &amp;#34;🔧 Verifying MSRV...&amp;#34;&lt;br/&gt;    cargo-msrv verify&lt;br/&gt;    echo &amp;#34;✅ MSRV verification passed&amp;#34;&lt;br/&gt;&lt;br/&gt;_done_ci:&lt;br/&gt;    echo &amp;#34;🎉 CI pipeline completed successfully&amp;#34;&lt;br/&gt;&lt;br/&gt;# Update the changelog&lt;br/&gt;[script]&lt;br/&gt;changelog:&lt;br/&gt;    def get_hash [] { open &amp;#34;./CHANGELOG.md&amp;#34; | hash sha256 }&lt;br/&gt;&lt;br/&gt;    let old_hash = get_hash&lt;br/&gt;    git-cliff out&amp;gt; &amp;#34;CHANGELOG.md&amp;#34;&lt;br/&gt;&lt;br/&gt;    if old_hash != get_hash {&lt;br/&gt;        git add &amp;#34;CHANGELOG.md&amp;#34;&lt;br/&gt;        git commit -m &amp;#34;chore(changelog): Update the changelog&amp;#34;&lt;br/&gt;        print &amp;#34;The changes have been added to the changelog file and committed&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        print &amp;#34;No changes have been added to the changelog&amp;#34;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;# Releases a new version of n34. Requires a clean file tree with no uncommitted changes.&lt;br/&gt;[script]&lt;br/&gt;release version:&lt;br/&gt;    let tag_msg = &amp;#34;Version {{ version }}&amp;#34; &#43; (git-cliff --strip all --unreleased --body &amp;#39;{{ tag_change_body }}&amp;#39;)&lt;br/&gt;    mut cargo_file = open &amp;#34;Cargo.toml&amp;#34;&lt;br/&gt;&lt;br/&gt;    $cargo_file.package.version = &amp;#34;{{ version }}&amp;#34;&lt;br/&gt;    $cargo_file | save -f &amp;#34;Cargo.toml&amp;#34;&lt;br/&gt;&lt;br/&gt;    RUST_LOG=none taplo fmt --config &amp;#34;./.taplo.toml&amp;#34;&lt;br/&gt;    {{ JUST_EXECUTABLE }} ci&lt;br/&gt;    git-cliff -t &amp;#34;v{{ version }}&amp;#34; out&amp;gt; &amp;#34;./CHANGELOG.md&amp;#34;&lt;br/&gt;    git add .&lt;br/&gt;    git commit -m &amp;#34;chore: Bump the version to `v{{ version }}`&amp;#34;&lt;br/&gt;    git tag -s -m $tag_msg &amp;#34;v{{ version }}&amp;#34;&lt;br/&gt;    git push origin master --tags&lt;br/&gt;    cargo publish&lt;br/&gt;&lt;br/&gt;# Deploy the book to Github Pages&lt;br/&gt;deploy:&lt;br/&gt;    mdbook build --dest-dir {{ BOOK_DEST_DIR }}&lt;br/&gt;    cd {{ BOOK_DEST_DIR }}&lt;br/&gt;    git init .&lt;br/&gt;    git checkout -B gh-pages&lt;br/&gt;    touch &amp;#34;.nojekyll&amp;#34;&lt;br/&gt;    echo &amp;#34;n34.dev&amp;#34; &amp;gt; &amp;#34;CNAME&amp;#34;&lt;br/&gt;&lt;br/&gt;    git add .&lt;br/&gt;    git commit -m &amp;#34;Deploy the book to github pages&amp;#34;&lt;br/&gt;    git remote add origin &amp;#34;git@github.com:TheAwiteb/n34-book&amp;#34;&lt;br/&gt;    git push origin gh-pages -f&lt;br/&gt;    cd ..&lt;br/&gt;    rm -fr {{ BOOK_DEST_DIR }}&lt;br/&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:46:29&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstcxkpjrks3vtn6cnm8guqdrw3z6chakuhz6nk0eqe9sxz62ef8lqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsgzm9jx</id>
    
      <title type="html">{ inputs = { nixpkgs.url = ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstcxkpjrks3vtn6cnm8guqdrw3z6chakuhz6nk0eqe9sxz62ef8lqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsgzm9jx" />
    <content type="html">
      {&lt;br/&gt;  inputs = {&lt;br/&gt;    nixpkgs.url = &amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;;&lt;br/&gt;    rust-overlay.url = &amp;#34;github:oxalica/rust-overlay&amp;#34;;&lt;br/&gt;    flake-utils.url = &amp;#34;github:numtide/flake-utils&amp;#34;;&lt;br/&gt;  };&lt;br/&gt;&lt;br/&gt;  outputs =&lt;br/&gt;    {&lt;br/&gt;      nixpkgs,&lt;br/&gt;      rust-overlay,&lt;br/&gt;      flake-utils,&lt;br/&gt;      ...&lt;br/&gt;    }:&lt;br/&gt;    flake-utils.lib.eachDefaultSystem (&lt;br/&gt;      system:&lt;br/&gt;      let&lt;br/&gt;        overlays = [ (import rust-overlay) ];&lt;br/&gt;        pkgs = import nixpkgs { inherit system overlays; };&lt;br/&gt;      in&lt;br/&gt;      with pkgs;&lt;br/&gt;      {&lt;br/&gt;        devShells.default = mkShell {&lt;br/&gt;          packages = [&lt;br/&gt;            cargo-msrv&lt;br/&gt;            dbus&lt;br/&gt;            git-cliff&lt;br/&gt;            just&lt;br/&gt;            mdbook&lt;br/&gt;            nushell&lt;br/&gt;            pkg-config&lt;br/&gt;            taplo&lt;br/&gt;          ];&lt;br/&gt;&lt;br/&gt;          nativeBuildInputs = [&lt;br/&gt;            (lib.hiPrio rust-bin.nightly.&amp;#34;2025-08-07&amp;#34;.rustfmt)&lt;br/&gt;            rust-bin.stable.latest.default&lt;br/&gt;            rust-analyzer&lt;br/&gt;          ];&lt;br/&gt;        };&lt;br/&gt;&lt;br/&gt;        packages.default =&lt;br/&gt;          let&lt;br/&gt;            manifest = (pkgs.lib.importTOML ./Cargo.toml).package;&lt;br/&gt;          in&lt;br/&gt;          with pkgs;&lt;br/&gt;          rustPlatform.buildRustPackage {&lt;br/&gt;            pname = manifest.name;&lt;br/&gt;            version = manifest.version;&lt;br/&gt;            cargoLock.lockFile = ./Cargo.lock;&lt;br/&gt;            src = lib.cleanSource ./.;&lt;br/&gt;&lt;br/&gt;            nativeBuildInputs = [&lt;br/&gt;              pkg-config&lt;br/&gt;            ];&lt;br/&gt;&lt;br/&gt;            buildInputs = [&lt;br/&gt;              dbus&lt;br/&gt;            ];&lt;br/&gt;&lt;br/&gt;            meta = {&lt;br/&gt;              inherit (manifest) description homepage;&lt;br/&gt;              license = lib.licenses.gpl3Plus;&lt;br/&gt;            };&lt;br/&gt;          };&lt;br/&gt;      }&lt;br/&gt;    );&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:46:18&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsysdl0lg5yz7lk4yf2ga4dcu38k64uqp5uvat3ykjc5x33th42v2qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrss2hgu2</id>
    
      <title type="html">{ &amp;#34;nodes&amp;#34;: { &amp;#34;flake-utils&amp;#34;: { &amp;#34;inputs&amp;#34;: { ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsysdl0lg5yz7lk4yf2ga4dcu38k64uqp5uvat3ykjc5x33th42v2qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrss2hgu2" />
    <content type="html">
      {&lt;br/&gt;  &amp;#34;nodes&amp;#34;: {&lt;br/&gt;    &amp;#34;flake-utils&amp;#34;: {&lt;br/&gt;      &amp;#34;inputs&amp;#34;: {&lt;br/&gt;        &amp;#34;systems&amp;#34;: &amp;#34;systems&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;locked&amp;#34;: {&lt;br/&gt;        &amp;#34;lastModified&amp;#34;: 1731533236,&lt;br/&gt;        &amp;#34;narHash&amp;#34;: &amp;#34;sha256-l0KFg5HjrsfsO/JpG&#43;r7fRrqm12kzFHyUHqHCVpMMbI=&amp;#34;,&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;numtide&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;flake-utils&amp;#34;,&lt;br/&gt;        &amp;#34;rev&amp;#34;: &amp;#34;11707dc2f618dd54ca8739b309ec4fc024de578b&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;original&amp;#34;: {&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;numtide&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;flake-utils&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    },&lt;br/&gt;    &amp;#34;nixpkgs&amp;#34;: {&lt;br/&gt;      &amp;#34;locked&amp;#34;: {&lt;br/&gt;        &amp;#34;lastModified&amp;#34;: 1754498491,&lt;br/&gt;        &amp;#34;narHash&amp;#34;: &amp;#34;sha256-erbiH2agUTD0Z30xcVSFcDHzkRvkRXOQ3lb887bcVrs=&amp;#34;,&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;NixOS&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;nixpkgs&amp;#34;,&lt;br/&gt;        &amp;#34;rev&amp;#34;: &amp;#34;c2ae88e026f9525daf89587f3cbee584b92b6134&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;original&amp;#34;: {&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;NixOS&amp;#34;,&lt;br/&gt;        &amp;#34;ref&amp;#34;: &amp;#34;nixos-unstable&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;nixpkgs&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    },&lt;br/&gt;    &amp;#34;nixpkgs_2&amp;#34;: {&lt;br/&gt;      &amp;#34;locked&amp;#34;: {&lt;br/&gt;        &amp;#34;lastModified&amp;#34;: 1744536153,&lt;br/&gt;        &amp;#34;narHash&amp;#34;: &amp;#34;sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=&amp;#34;,&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;NixOS&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;nixpkgs&amp;#34;,&lt;br/&gt;        &amp;#34;rev&amp;#34;: &amp;#34;18dd725c29603f582cf1900e0d25f9f1063dbf11&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;original&amp;#34;: {&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;NixOS&amp;#34;,&lt;br/&gt;        &amp;#34;ref&amp;#34;: &amp;#34;nixpkgs-unstable&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;nixpkgs&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    },&lt;br/&gt;    &amp;#34;root&amp;#34;: {&lt;br/&gt;      &amp;#34;inputs&amp;#34;: {&lt;br/&gt;        &amp;#34;flake-utils&amp;#34;: &amp;#34;flake-utils&amp;#34;,&lt;br/&gt;        &amp;#34;nixpkgs&amp;#34;: &amp;#34;nixpkgs&amp;#34;,&lt;br/&gt;        &amp;#34;rust-overlay&amp;#34;: &amp;#34;rust-overlay&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    },&lt;br/&gt;    &amp;#34;rust-overlay&amp;#34;: {&lt;br/&gt;      &amp;#34;inputs&amp;#34;: {&lt;br/&gt;        &amp;#34;nixpkgs&amp;#34;: &amp;#34;nixpkgs_2&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;locked&amp;#34;: {&lt;br/&gt;        &amp;#34;lastModified&amp;#34;: 1754575663,&lt;br/&gt;        &amp;#34;narHash&amp;#34;: &amp;#34;sha256-afOx8AG0KYtw7mlt6s6ahBBy7eEHZwws3iCRoiuRQS4=&amp;#34;,&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;oxalica&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;rust-overlay&amp;#34;,&lt;br/&gt;        &amp;#34;rev&amp;#34;: &amp;#34;6db0fb0e9cec2e9729dc52bf4898e6c135bb8a0f&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;original&amp;#34;: {&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;oxalica&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;rust-overlay&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    },&lt;br/&gt;    &amp;#34;systems&amp;#34;: {&lt;br/&gt;      &amp;#34;locked&amp;#34;: {&lt;br/&gt;        &amp;#34;lastModified&amp;#34;: 1681028828,&lt;br/&gt;        &amp;#34;narHash&amp;#34;: &amp;#34;sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=&amp;#34;,&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;nix-systems&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;default&amp;#34;,&lt;br/&gt;        &amp;#34;rev&amp;#34;: &amp;#34;da67096a3b9bf56a91d16901293e51ba5b49a27e&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      },&lt;br/&gt;      &amp;#34;original&amp;#34;: {&lt;br/&gt;        &amp;#34;owner&amp;#34;: &amp;#34;nix-systems&amp;#34;,&lt;br/&gt;        &amp;#34;repo&amp;#34;: &amp;#34;default&amp;#34;,&lt;br/&gt;        &amp;#34;type&amp;#34;: &amp;#34;github&amp;#34;&lt;br/&gt;      }&lt;br/&gt;    }&lt;br/&gt;  },&lt;br/&gt;  &amp;#34;root&amp;#34;: &amp;#34;root&amp;#34;,&lt;br/&gt;  &amp;#34;version&amp;#34;: 7&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:46:07&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqf3thyt8ahex2zt8kkpevt4axgcyuk4vehv6gh8vx24ec556lv2gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2rd0x0</id>
    
      <title type="html"># Modify a Set &amp;gt; `n34 sets update` command **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqf3thyt8ahex2zt8kkpevt4axgcyuk4vehv6gh8vx24ec556lv2gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs2rd0x0" />
    <content type="html">
      # Modify a Set&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 sets update` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Modify an existing set&lt;br/&gt;&lt;br/&gt;Usage: n34 sets update [OPTIONS] &amp;lt;NAME&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;NAME&amp;gt;  Name of the set to update&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --set-relay &amp;lt;RELAYS&amp;gt;         Add relay to the set, either as URL or set name to extract its relays. [aliases: `--sr`]&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;      --override                   Replace existing relays/repositories instead of adding to them&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Use this command to update an existing set by its name. By default, providing&lt;br/&gt;relays via `--set-relay` or repositories via `--repo` will add them to the set&amp;#39;s&lt;br/&gt;existing entries. To replace the current relays and repositories with the new&lt;br/&gt;values, use the `--override` flag.&lt;br/&gt;&lt;br/&gt;[passing repositories]: /commands.html#passing-repositories&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:56&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsf2ue47dvvq9a6utdqfswvr6tmgxf8c6a6u34zyuf862qm7wcmdjqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0ul65d</id>
    
      <title type="html"># Show a Set &amp;gt; `n34 sets show` command **Usage:** ``` Show a ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsf2ue47dvvq9a6utdqfswvr6tmgxf8c6a6u34zyuf862qm7wcmdjqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0ul65d" />
    <content type="html">
      # Show a Set&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 sets show` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Show a single set or all the stored sets&lt;br/&gt;&lt;br/&gt;Usage: n34 sets show [NAME]&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [NAME]  Name of the set to display. If not provided, lists all available sets&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Prints all set names to standard output, each followed by its associated repos&lt;br/&gt;and relays. To view the details for a specific set, provide its name as an&lt;br/&gt;argument.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:45&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqapvfxyu039k6w4pagssrfw8x9r42kt7vayftqdvk3p8xl7rw44gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0439ra</id>
    
      <title type="html"># Remove a Set &amp;gt; `n34 sets remove` command **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqapvfxyu039k6w4pagssrfw8x9r42kt7vayftqdvk3p8xl7rw44gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0439ra" />
    <content type="html">
      # Remove a Set&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 sets remove` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Remove a set, or specific repos and relays within it&lt;br/&gt;&lt;br/&gt;Usage: n34 sets remove [OPTIONS] &amp;lt;NAME&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;NAME&amp;gt;  Set name to delete&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --set-relay &amp;lt;RELAYS&amp;gt;         Specific relay to remove it from the set, either as URL or set name to extract its relays. [aliases: `--sr`]&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Removes an entire set, or specific repositories and relays from it.&lt;br/&gt;Without options, this command deletes the entire set.&lt;br/&gt;&lt;br/&gt;See the [passing repositories] section for more details on supported formats.&lt;br/&gt;&lt;br/&gt;[passing repositories]: /commands.html#passing-repositories&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:34&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqh6yzyxgq3l2x3tdkr2eu5trwgpj9vg9m9l3ae0tecadh5e84xrszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsn6vnh7</id>
    
      <title type="html"># Create a Set &amp;gt; `n34 sets new` command **Usage:** ``` Create ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqh6yzyxgq3l2x3tdkr2eu5trwgpj9vg9m9l3ae0tecadh5e84xrszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsn6vnh7" />
    <content type="html">
      # Create a Set&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 sets new` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Create a new set&lt;br/&gt;&lt;br/&gt;Usage: n34 sets new [OPTIONS] &amp;lt;NAME&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;NAME&amp;gt;  Unique name for the set&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --set-relay &amp;lt;RELAYS&amp;gt;         Optional relay to add it to the set, either as URL or set name to extract its relays. [aliases: `--sr`]&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Each set requires a unique name, provided as the final argument to the command.&lt;br/&gt;Use the `--set-relays`/`--sr` option to specify the relays for the new set;&lt;br/&gt;this can be a relay URL or the name of an existing set whose relays you wish to&lt;br/&gt;use. To add repositories, use the `--repo` option. Check [passing repositories]&lt;br/&gt;format.&lt;br/&gt;&lt;br/&gt;[passing repositories]: /commands.html#passing-repositories&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:24&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstmc3mthr4ycfspajmjhd74gcuxq0ynddd2dlcw9yzxv56dhaduzczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsyqq8m9</id>
    
      <title type="html"># Managing Repository and Relay Sets Sets are a convenience ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstmc3mthr4ycfspajmjhd74gcuxq0ynddd2dlcw9yzxv56dhaduzczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsyqq8m9" />
    <content type="html">
      # Managing Repository and Relay Sets&lt;br/&gt;&lt;br/&gt;Sets are a convenience feature for contributing to projects that do not have a&lt;br/&gt;`nostr-address` file. Instead of manually specifying the project&amp;#39;s repositories&lt;br/&gt;and relays for every command, you can define them once as a named &amp;#34;set&amp;#34;. You can&lt;br/&gt;then reference this set by its name in commands. This allows you to use the set&lt;br/&gt;as a shortcut for a list of relays (`--relays &amp;lt;set_name&amp;gt;`) or as the project&amp;#39;s&lt;br/&gt;address in commands like `issue` and `patch`.&lt;br/&gt;&lt;br/&gt;Sets are defined in your configuration file. To use a specific configuration&lt;br/&gt;file, pass its path using the `--config` option.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:13&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspt8t2xjr3aw3c488v769sn4g04a9z8wrumn5fqd34tfsfetyhxfczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszl69js</id>
    
      <title type="html"># View Git Repository Details &amp;gt; `n34 repo view` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspt8t2xjr3aw3c488v769sn4g04a9z8wrumn5fqd34tfsfetyhxfczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszl69js" />
    <content type="html">
      # View Git Repository Details&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 repo view` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;View details of a nostr git repository&lt;br/&gt;&lt;br/&gt;Usage: n34 repo view [NADDR-NIP05-OR-SET]...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [NADDR-NIP05-OR-SET]...  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;This command prints repository details to standard output. If no arguments&lt;br/&gt;are provided, it looks for a `nostr-address` file in the current directory&lt;br/&gt;and displays the details for the address specified within it. See [passing&lt;br/&gt;repositories] for details on accepted formats.&lt;br/&gt;&lt;br/&gt;[passing repositories]: /commands.html#passing-repositories&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:45:02&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2zw4vf2ll3lcgf8y27zswxtplqvy7v752nl2tp8v5gahzkjjmxkczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs9l6jxj</id>
    
      <title type="html"># Broadcast and Update a Git Repository &amp;gt; `n34 repo announce` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2zw4vf2ll3lcgf8y27zswxtplqvy7v752nl2tp8v5gahzkjjmxkczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs9l6jxj" />
    <content type="html">
      # Broadcast and Update a Git Repository&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 repo announce` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Broadcast and update a git repository&lt;br/&gt;&lt;br/&gt;Usage: n34 repo announce [OPTIONS] --id &amp;lt;REPO_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --id &amp;lt;REPO_ID&amp;gt;               Unique identifier for the repository in kebab-case&lt;br/&gt;  -n, --name &amp;lt;NAME&amp;gt;                A name for the repository&lt;br/&gt;  -d, --description &amp;lt;DESCRIPTION&amp;gt;  A description for the repository&lt;br/&gt;  -w, --web &amp;lt;WEB&amp;gt;                  Webpage URLs for the repository (if provided by the git server)&lt;br/&gt;  -c, --clone &amp;lt;CLONE&amp;gt;              URLs for cloning the repository&lt;br/&gt;  -m, --maintainers &amp;lt;MAINTAINERS&amp;gt;  Additional maintainers of the repository (besides yourself)&lt;br/&gt;  -l, --label &amp;lt;LABEL&amp;gt;              Labels to categorize the repository. Can be specified multiple times&lt;br/&gt;      --force-id                   Skip kebab-case validation for the repository ID&lt;br/&gt;      --address-file               If set, creates a `nostr-address` file to enable automatic address discovery by n34&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;This command generates an announcement event to publish your project. It can be&lt;br/&gt;used to announce a new repository or update an existing one.&lt;br/&gt;&lt;br/&gt;When updating, you must resubmit all repository fields, not just the fields&lt;br/&gt;you wish to change. The command uses this information to build and publish a&lt;br/&gt;completely new announcement event that will replace the old one.&lt;br/&gt;&lt;br/&gt;It is recommended to use the `--address-file` flag. This option creates&lt;br/&gt;a `nostr-address` file that enables `n34` to automatically discover the&lt;br/&gt;repository&amp;#39;s address, simplifying the workflow for contributors.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:44:51&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs0tp79373r5yw68f254dzgjljws879wku8hrc80gkwcez55zadxrczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp9a4gl</id>
    
      <title type="html"># Manage Repositories In `n34` you can manage your repositories. ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs0tp79373r5yw68f254dzgjljws879wku8hrc80gkwcez55zadxrczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp9a4gl" />
    <content type="html">
      # Manage Repositories&lt;br/&gt;&lt;br/&gt;In `n34` you can manage your repositories. This includes announcing new ones,&lt;br/&gt;viewing existing ones, and announcing state updates (coming soon).&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:44:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8cynd46jwzcsf8jhzqyld2htlpskhmfhm5gq6rw8fvrzdhdph4lgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsum8d44</id>
    
      <title type="html"># Reply to Issues and Patches &amp;gt; `n34 reply` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8cynd46jwzcsf8jhzqyld2htlpskhmfhm5gq6rw8fvrzdhdph4lgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsum8d44" />
    <content type="html">
      # Reply to Issues and Patches&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 reply` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Reply to issues and patches&lt;br/&gt;&lt;br/&gt;Usage: n34 reply [OPTIONS] &amp;lt;--comment &amp;lt;COMMENT&amp;gt;|--editor&amp;gt; &amp;lt;nevent1-or-note1&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;nevent1-or-note1&amp;gt;  The issue, patch, or comment to reply to&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --quote-to                   Quote the replied-to event in the editor&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;  -c, --comment &amp;lt;COMMENT&amp;gt;          The comment (cannot be used with --editor)&lt;br/&gt;  -e, --editor                     Open editor to write comment (cannot be used with --content)&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Craft replies ([NIP-22] Comment) to issues, patches, or comments with ease&lt;br/&gt;using the `n34 reply` command. You can either input your reply directly with&lt;br/&gt;the `--comment` option or open an editor for a more detailed response using&lt;br/&gt;`--editor`. Additionally, when using `--editor`, the `--quote-to` option&lt;br/&gt;allows you to include the original content in your editor, enabling precise and&lt;br/&gt;context-aware replies.&lt;br/&gt;&lt;br/&gt;[NIP-22]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/22.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/22.md&lt;/a&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:44:30&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfz36ju05qzurz5zhswvg6vgk6w28hctjxn4ycak984pdt8lctz6szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsqjx73a</id>
    
      <title type="html"># Send Patches to a Repository &amp;gt; `n34 patch send` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfz36ju05qzurz5zhswvg6vgk6w28hctjxn4ycak984pdt8lctz6szyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsqjx73a" />
    <content type="html">
      # Send Patches to a Repository&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch send` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Send one or more patches to a repository&lt;br/&gt;&lt;br/&gt;Usage: n34 patch send [OPTIONS] &amp;lt;PATCH-PATH&amp;gt;...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH-PATH&amp;gt;...  List of patch files to send (space separated)&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;      --original-patch &amp;lt;EVENT-ID&amp;gt;  Original patch ID if this is a revision of it&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Send your generated patches to the repositories specified using the `--repo`&lt;br/&gt;option or retrieved from the `nostr-address` file. When submitting a revision&lt;br/&gt;of an existing patch, include the original patch ID to ensure it’s correctly&lt;br/&gt;referenced in your revision patch event.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:44:19&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsycj976d7jwcgfp0yaew4lj6fuyjcn9rer3e6fyj8fu4z5g0a2vcszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrskfcjze</id>
    
      <title type="html"># Reopens a Closed or Drafted Patch &amp;gt; `n34 patch reopen` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsycj976d7jwcgfp0yaew4lj6fuyjcn9rer3e6fyj8fu4z5g0a2vcszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrskfcjze" />
    <content type="html">
      # Reopens a Closed or Drafted Patch&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch reopen` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Reopens a closed or drafted patch&lt;br/&gt;&lt;br/&gt;Usage: n34 patch reopen [OPTIONS] &amp;lt;PATCH_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;  The closed/drafted patch id to reopen it. Must be orignal root patch&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1632` (Close status) for the specified patch. The patch have to&lt;br/&gt;be closed or drafted.&lt;br/&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:44:08&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdsmcxzm84lv0vx4yeff9uydyj6u5r2w8tfjpnky9lj884gv6zrzqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsx4ur9e</id>
    
      <title type="html"># Merge an Open Patch &amp;gt; `n34 patch merge` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdsmcxzm84lv0vx4yeff9uydyj6u5r2w8tfjpnky9lj884gv6zrzqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsx4ur9e" />
    <content type="html">
      # Merge an Open Patch&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch merge` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Set an open patch status to merged&lt;br/&gt;&lt;br/&gt;Usage: n34 patch merge [OPTIONS] &amp;lt;PATCH_ID&amp;gt; &amp;lt;MERGE_COMMIT&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;      The open patch id to merge it. Must be orignal root patch or revision root&lt;br/&gt;  &amp;lt;MERGE_COMMIT&amp;gt;  The merge commit id&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;      --patches &amp;lt;PATCH-EVENT-ID&amp;gt;   Patches that have been merged. Use this when only some patches have been merged, not all&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Creates a kind `1631` event (Applied/Merged status) for the specified patch. The&lt;br/&gt;patch must be in open status.&lt;br/&gt;&lt;br/&gt;You can specify either an original patch or revision patch ID, but the status&lt;br/&gt;event will only reference the original patch. Revision patches will be mentioned&lt;br/&gt;in the event.&lt;br/&gt;&lt;br/&gt;You can get the `MERGE_COMMIT` commit using `git rev-parse HEAD` command if&lt;br/&gt;the merge commit in the `HEAD` or use `HEAD~n` where the `n` is the number of&lt;br/&gt;commits the merge commit before the HEAD&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:57&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsyckt7r2dx400t2z4q7v40cwzagj4kaz864f8wny6pq0z5eawku6qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp2kajk</id>
    
      <title type="html"># List Repositories Patches &amp;gt; `n34 patch list` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsyckt7r2dx400t2z4q7v40cwzagj4kaz864f8wny6pq0z5eawku6qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsp2kajk" />
    <content type="html">
      # List Repositories Patches&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch list` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;List the repositories patches&lt;br/&gt;&lt;br/&gt;Usage: n34 patch list [OPTIONS] [NADDR-NIP05-OR-SET]...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [NADDR-NIP05-OR-SET]...  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --limit &amp;lt;LIMIT&amp;gt;  Maximum number of patches to list [default: 15]&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;List the repositories patches. By default `n34` will look for `nostr-address`&lt;br/&gt;file and extract the repositories from it.&lt;br/&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:47&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsz4n9exlkh08qjamglha3pkx3z9dwqz2dq7jvga544xrnxw0n2qqgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsumvep0</id>
    
      <title type="html"># Fetch a Patch By ID &amp;gt; `n34 patch fetch` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsz4n9exlkh08qjamglha3pkx3z9dwqz2dq7jvga544xrnxw0n2qqgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsumvep0" />
    <content type="html">
      # Fetch a Patch By ID&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch fetch` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Fetches a patch by its id&lt;br/&gt;&lt;br/&gt;Usage: n34 patch fetch [OPTIONS] &amp;lt;PATCH_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;  The patch id to fetch it&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;  -o, --output &amp;lt;PATH&amp;gt;              Output directory for the patches. Default to the current directory&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Fetches patches using their original patch ID. All fetched patches will be saved&lt;br/&gt;to the specified output directory (current directory by default). You can then&lt;br/&gt;apply or merge these patches into your branch as needed.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:36&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqswacyc5pz5duwek0zq5l0kz7ux27sj9hume49g5lg2kwda5kqzcfczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6j6956</id>
    
      <title type="html"># Draft an Open Patch &amp;gt; `n34 patch draft` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqswacyc5pz5duwek0zq5l0kz7ux27sj9hume49g5lg2kwda5kqzcfczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs6j6956" />
    <content type="html">
      # Draft an Open Patch&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch draft` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Converts an open patch to draft state&lt;br/&gt;&lt;br/&gt;Usage: n34 patch draft [OPTIONS] &amp;lt;PATCH_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;  The open patch id to draft it. Must be orignal root patch&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1633` (Draft status) for the specified patch. The patch have to&lt;br/&gt;be open.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:26&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxdu7huu56cm23eg8v5xf9zppzfuut7njzwjyvp2mxezrs6tpjp4gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4rld65</id>
    
      <title type="html"># Closes an Open or Drafted Patch &amp;gt; `n34 patch close` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxdu7huu56cm23eg8v5xf9zppzfuut7njzwjyvp2mxezrs6tpjp4gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs4rld65" />
    <content type="html">
      # Closes an Open or Drafted Patch&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch close` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Closes an open or drafted patch&lt;br/&gt;&lt;br/&gt;Usage: n34 patch close [OPTIONS] &amp;lt;PATCH_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;  The open/drafted patch id to close it. Must be orignal root patch&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1632` (Close status) for the specified patch. The patch have to&lt;br/&gt;be open or drafted.&lt;br/&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:15&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdt6x9dy97qhg4uq8kz67qv5x4atwl2hnnysmflmz7z7nzmgf4nfszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszt0jhf</id>
    
      <title type="html"># Apply an Open Patch &amp;gt; `n34 patch apply` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdt6x9dy97qhg4uq8kz67qv5x4atwl2hnnysmflmz7z7nzmgf4nfszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrszt0jhf" />
    <content type="html">
      # Apply an Open Patch&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 patch apply` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Set an open patch status to applied&lt;br/&gt;&lt;br/&gt;Usage: n34 patch apply [OPTIONS] &amp;lt;PATCH_ID&amp;gt; [APPLIED_COMMITS]...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;PATCH_ID&amp;gt;            The open patch id to apply it. Must be orignal root patch or revision root&lt;br/&gt;  [APPLIED_COMMITS]...  The applied commits&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;      --patches &amp;lt;PATCH-EVENT-ID&amp;gt;   Patches that have been applied. Use this when only some patches have been applied, not all&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Creates a kind `1631` event (Applied/Merged status) for the specified patch. The&lt;br/&gt;patch must be in open status.&lt;br/&gt;&lt;br/&gt;You can specify either an original patch or revision patch ID, but the status&lt;br/&gt;event will only reference the original patch. Revision patches will be mentioned&lt;br/&gt;in the event.&lt;br/&gt;&lt;br/&gt;The `APPLIED_COMMITS` field serves to inform clients about the status of&lt;br/&gt;specific commits, whether they have been applied or not. If you need to retrieve&lt;br/&gt;the list of commits from a specific point (such as the tip of the master branch)&lt;br/&gt;up to the `HEAD`, you can use the following Git command: `git log --pretty=%H&lt;br/&gt;&amp;#39;origin/master..HEAD&amp;#39;`.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:43:04&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsq7x0gzes7afaka5vdenymjwcfjr5825d4pncw2kkjm6209kyqxfszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsjan0j8</id>
    
      <title type="html"># Patch Management In `n34`, patch management is designed to give ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsq7x0gzes7afaka5vdenymjwcfjr5825d4pncw2kkjm6209kyqxfszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsjan0j8" />
    <content type="html">
      # Patch Management&lt;br/&gt;&lt;br/&gt;In `n34`, patch management is designed to give you complete control. You can&lt;br/&gt;manually generate patch files using `git-format-patch` and then broadcast them&lt;br/&gt;to Nostr relays. This ensures that you have full authority over the content&lt;br/&gt;and structure of your patches, allowing for precise customization as per your&lt;br/&gt;requirements.&lt;br/&gt;&lt;br/&gt;Similarly, when fetching patches, `n34` provides them to you without&lt;br/&gt;automatically applying, merging, or checking them. This empowers you to review&lt;br/&gt;the patches at your own pace and decide whether to merge or apply them as&lt;br/&gt;needed. You retain full control over the entire process, ensuring a tailored&lt;br/&gt;approach to patch management.&lt;br/&gt;&lt;br/&gt;## Patch Status Management&lt;br/&gt;&lt;br/&gt;You can assign a status to original patches, but revision patches do not have&lt;br/&gt;a specific status assigned to them. Instead, they inherit the status of the&lt;br/&gt;original patch. However, if the original patch is marked as `Applied/Merged`,&lt;br/&gt;the revision patch must be explicitly tagged to claim the same status. If not&lt;br/&gt;tagged, the revision patch status will be `Closed`.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:42:53&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgmrv25mrdxe50uwwgce7h975xpmf70jcmt5emzk7yspl72fg973czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs8xsgsm</id>
    
      <title type="html"># View an Issue By ID &amp;gt; `n34 issue view` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgmrv25mrdxe50uwwgce7h975xpmf70jcmt5emzk7yspl72fg973czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs8xsgsm" />
    <content type="html">
      # View an Issue By ID&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue view` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;View an issue by its ID&lt;br/&gt;&lt;br/&gt;Usage: n34 issue view [OPTIONS] &amp;lt;ISSUE_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;ISSUE_ID&amp;gt;  The issue id to view it&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Simply provide the issue ID in `note` or `nevent` format to retrieve and display&lt;br/&gt;the issue details.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:42:43&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdjqk6w33flflkgtnuujh9ys6wghra5fpt5qdk4xgcx6vvzavnjkczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsgk9sd6</id>
    
      <title type="html"># Resolves an Issue &amp;gt; `n34 issue resolve` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdjqk6w33flflkgtnuujh9ys6wghra5fpt5qdk4xgcx6vvzavnjkczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsgk9sd6" />
    <content type="html">
      # Resolves an Issue&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue resolve` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Resolves an issue&lt;br/&gt;&lt;br/&gt;Usage: n34 issue resolve [OPTIONS] &amp;lt;ISSUE_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;ISSUE_ID&amp;gt;  The issue id to resolve it&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1631` (Resolved status) event for the specified issue.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:42:31&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8zhtgq529xqa8x0d5d5lc5sh2nw6m8gwz3y7fug5v7nzhjzkmc6gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsz26rtz</id>
    
      <title type="html"># Reopen a Closed Issue &amp;gt; `n34 issue reopen` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8zhtgq529xqa8x0d5d5lc5sh2nw6m8gwz3y7fug5v7nzhjzkmc6gzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsz26rtz" />
    <content type="html">
      # Reopen a Closed Issue&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue reopen` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Reopens a closed issue&lt;br/&gt;&lt;br/&gt;Usage: n34 issue reopen [OPTIONS] &amp;lt;ISSUE_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;ISSUE_ID&amp;gt;  The ID of the closed issue to reopen&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1630` (Open status) for the specified issue. The issue have to&lt;br/&gt;be closed.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:42:20&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxt2m4m97cxjrzkdladzr630gwtflf6m5nsd536nwldntunas04nczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrss8nk79</id>
    
      <title type="html"># Create an Issue &amp;gt; `n34 issue new` Command **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxt2m4m97cxjrzkdladzr630gwtflf6m5nsd536nwldntunas04nczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrss8nk79" />
    <content type="html">
      # Create an Issue&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue new` Command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Create a new repository issue&lt;br/&gt;&lt;br/&gt;Usage: n34 issue new [OPTIONS] &amp;lt;--content &amp;lt;CONTENT&amp;gt;|--editor&amp;gt;&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;  -c, --content &amp;lt;CONTENT&amp;gt;          Markdown content for the issue. Cannot be used together with the `--editor` flag&lt;br/&gt;  -e, --editor                     Opens the user&amp;#39;s default editor to write issue content. The first line will be used as the issue subject&lt;br/&gt;      --subject &amp;lt;SUBJECT&amp;gt;          The issue subject. Cannot be used together with the `--editor` flag&lt;br/&gt;  -l, --label &amp;lt;LABEL&amp;gt;              Labels for the issue. Can be specified as arguments (-l bug) or hashtags in content (#bug)&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Use the `n34 issue new` command to create a new issue in a repository. This&lt;br/&gt;command supports the [NIP-21] (`nostr:` URI scheme) and hashtags within the&lt;br/&gt;issue content. When you mention public keys in the content, they will be&lt;br/&gt;included in the event tags. Additionally, using hashtags like `#bug` in the&lt;br/&gt;issue body will automatically apply them as labels.&lt;br/&gt;&lt;br/&gt;You must choose between the `--content` and `--editor` options. With&lt;br/&gt;`--content`, you provide the issue content directly in the command. With&lt;br/&gt;`--editor`, your default `$EDITOR` will open, allowing you to write the issue&lt;br/&gt;content. The first line of the editor&amp;#39;s output will be used as the issue&lt;br/&gt;subject.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:42:09&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2z3zudaw0v7dnl0477g7prkenjkpmxrdw7ech2v9pxh8hf0wt4vgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsdzh5z4</id>
    
      <title type="html"># List Repositories Issues &amp;gt; `n34 issue list` command ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2z3zudaw0v7dnl0477g7prkenjkpmxrdw7ech2v9pxh8hf0wt4vgzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsdzh5z4" />
    <content type="html">
      # List Repositories Issues&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue list` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;List the repositories issues&lt;br/&gt;&lt;br/&gt;Usage: n34 issue list [OPTIONS] [NADDR-NIP05-OR-SET]...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [NADDR-NIP05-OR-SET]...  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --limit &amp;lt;LIMIT&amp;gt;  Maximum number of issues to list [default: 15]&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;List the repositories issues. By default `n34` will look for `nostr-address`&lt;br/&gt;file and extract the repositories from it.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:59&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxgfjwrrkrpwwyqhvdjh96cx8rxjces4pa4lxlzn0q6e06afddvhszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0fsm02</id>
    
      <title type="html"># Closes an Open Issue &amp;gt; `n34 issue close` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxgfjwrrkrpwwyqhvdjh96cx8rxjces4pa4lxlzn0q6e06afddvhszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs0fsm02" />
    <content type="html">
      # Closes an Open Issue&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 issue close` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Closes an open issue&lt;br/&gt;&lt;br/&gt;Usage: n34 issue close [OPTIONS] &amp;lt;ISSUE_ID&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;ISSUE_ID&amp;gt;  The open issue id to close it&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --repo &amp;lt;NADDR-NIP05-OR-SET&amp;gt;  Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Issue a kind `1632` (Close status) for the specified issue. The issue have to&lt;br/&gt;be open.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:48&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsg5k2h2wgg7efxp3gn46s4rgq97uvv25fdkcsuhmuekvhy0s39srczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrst6m36d</id>
    
      <title type="html"># Issue Management Using `n34`, you can manage Git issues stored ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsg5k2h2wgg7efxp3gn46s4rgq97uvv25fdkcsuhmuekvhy0s39srczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrst6m36d" />
    <content type="html">
      # Issue Management&lt;br/&gt;&lt;br/&gt;Using `n34`, you can manage Git issues stored in Nostr relays, adhering to&lt;br/&gt;the [NIP-34] standard. In Nostr, events are immutable, meaning their IDs are&lt;br/&gt;derived from the SHA-256 hash of their timestamp, content, author, and tags.&lt;br/&gt;As a result, issues cannot be edited directly. However, with `n34`, you can&lt;br/&gt;create new issues, view existing ones, or update their status—such as closing,&lt;br/&gt;resolving, or reopening them.&lt;br/&gt;&lt;br/&gt;[NIP-34] introduces support for drafting issues, though this feature is not&lt;br/&gt;currently implemented in `n34` due to the lack of a clear use case for drafting&lt;br/&gt;issues. The inclusion of this functionality may stem from its shared use in both&lt;br/&gt;issues and patches, suggesting it was primarily designed for patch management.&lt;br/&gt;&lt;br/&gt;[NIP-34]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/34.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/34.md&lt;/a&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:37&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqdu726ljgln09jvj6fn4lwptlufdm9j27xgs44fhgwwylgu2e9egzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrskj9pw9</id>
    
      <title type="html">&amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt; &amp;lt;head&amp;gt; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqdu726ljgln09jvj6fn4lwptlufdm9j27xgs44fhgwwylgu2e9egzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrskj9pw9" />
    <content type="html">
      &amp;lt;!DOCTYPE html&amp;gt;&lt;br/&gt;&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;&lt;br/&gt;  &amp;lt;head&amp;gt;&lt;br/&gt;    &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34; /&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;meta property=&amp;#34;og:title&amp;#34; content=&amp;#34;n34 - CLI for NIP-34 and Nostr Code Collaboration&amp;#34;&amp;gt;&lt;br/&gt;    &amp;lt;meta property=&amp;#34;og:description&amp;#34; content=&amp;#34;An open source CLI for sending and receiving Git issues, patches and comments over the Nostr protocol.&amp;#34;&amp;gt;&lt;br/&gt;    &amp;lt;meta property=&amp;#34;og:url&amp;#34; content=&amp;#34;&lt;a href=&#34;https://n34.dev&amp;#34;&amp;gt&#34;&gt;https://n34.dev&amp;#34;&amp;gt&lt;/a&gt;;&lt;br/&gt;    &amp;lt;meta property=&amp;#34;og:type&amp;#34; content=&amp;#34;website&amp;#34;&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;meta name=&amp;#34;twitter:card&amp;#34; content=&amp;#34;summary&amp;#34;&amp;gt;&lt;br/&gt;    &amp;lt;meta name=&amp;#34;twitter:title&amp;#34; content=&amp;#34;n34 - CLI for NIP-34 and Nostr Code Collaboration&amp;#34;&amp;gt;&lt;br/&gt;    &amp;lt;meta name=&amp;#34;twitter:description&amp;#34; content=&amp;#34;An open source CLI for sending and receiving Git issues, patches and comments over the Nostr protocol.&amp;#34;&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34; /&amp;gt;&lt;br/&gt;    &amp;lt;title&amp;gt;n34 - CLI for NIP-34 and Nostr Code Collaboration&amp;lt;/title&amp;gt;&lt;br/&gt;    &amp;lt;style&amp;gt;&lt;br/&gt;      * {&lt;br/&gt;        margin: 0;&lt;br/&gt;        padding: 0;&lt;br/&gt;        box-sizing: border-box;&lt;br/&gt;      }&lt;br/&gt;      body {&lt;br/&gt;        font-family: &amp;#34;Segoe UI&amp;#34;, Tahoma, Geneva, Verdana, sans-serif;&lt;br/&gt;        background: radial-gradient(125% 125% at 50% 90%, #000000 40%, #072607 100%);&lt;br/&gt;        color: #ffffff;&lt;br/&gt;        line-height: 1.6;&lt;br/&gt;        min-height: 100vh;&lt;br/&gt;      }&lt;br/&gt;      .container {&lt;br/&gt;        max-width: 800px;&lt;br/&gt;        margin: 0 auto;&lt;br/&gt;        padding: 60px 20px;&lt;br/&gt;      }&lt;br/&gt;      .hero {&lt;br/&gt;        text-align: center;&lt;br/&gt;        margin-bottom: 80px;&lt;br/&gt;      }&lt;br/&gt;      .hero h1 {&lt;br/&gt;        font-size: 4rem;&lt;br/&gt;        font-weight: 300;&lt;br/&gt;        margin-bottom: 20px;&lt;br/&gt;        letter-spacing: 2px;&lt;br/&gt;        background: linear-gradient(135deg, #ffffff, #cccccc);&lt;br/&gt;        -webkit-background-clip: text;&lt;br/&gt;        -webkit-text-fill-color: transparent;&lt;br/&gt;        background-clip: text;&lt;br/&gt;      }&lt;br/&gt;      .hero p {&lt;br/&gt;        font-size: 1.2rem;&lt;br/&gt;        color: #cccccc;&lt;br/&gt;        margin-bottom: 40px;&lt;br/&gt;        max-width: 600px;&lt;br/&gt;        margin-left: auto;&lt;br/&gt;        margin-right: auto;&lt;br/&gt;      }&lt;br/&gt;      .buttons {&lt;br/&gt;        display: flex;&lt;br/&gt;        gap: 20px;&lt;br/&gt;        justify-content: center;&lt;br/&gt;        flex-wrap: wrap;&lt;br/&gt;      }&lt;br/&gt;      .btn {&lt;br/&gt;        display: inline-block;&lt;br/&gt;        padding: 14px 28px;&lt;br/&gt;        background: transparent;&lt;br/&gt;        border: 2px solid #ffffff;&lt;br/&gt;        color: #ffffff;&lt;br/&gt;        text-decoration: none;&lt;br/&gt;        border-radius: 8px;&lt;br/&gt;        font-weight: 500;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        transition: all 0.3s ease;&lt;br/&gt;        min-width: 120px;&lt;br/&gt;        text-align: center;&lt;br/&gt;      }&lt;br/&gt;      .btn:hover {&lt;br/&gt;        background: #ffffff;&lt;br/&gt;        color: #0a0a0a;&lt;br/&gt;        transform: translateY(-2px);&lt;br/&gt;        box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);&lt;br/&gt;      }&lt;br/&gt;      .section {&lt;br/&gt;        margin-bottom: 60px;&lt;br/&gt;      }&lt;br/&gt;      .section h2 {&lt;br/&gt;        font-size: 2.2rem;&lt;br/&gt;        margin-bottom: 30px;&lt;br/&gt;        color: #ffffff;&lt;br/&gt;        font-weight: 400;&lt;br/&gt;      }&lt;br/&gt;      .section p {&lt;br/&gt;        font-size: 1.1rem;&lt;br/&gt;        color: #e0e0e0;&lt;br/&gt;        margin-bottom: 20px;&lt;br/&gt;        text-align: justify;&lt;br/&gt;      }&lt;br/&gt;      a {&lt;br/&gt;        color: #cccccc;&lt;br/&gt;        text-decoration: underline;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        transition: color 0.3s ease;&lt;br/&gt;      }&lt;br/&gt;      a:hover {&lt;br/&gt;        color: #ffffff;&lt;br/&gt;      }&lt;br/&gt;      .features {&lt;br/&gt;        display: grid;&lt;br/&gt;        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));&lt;br/&gt;        gap: 30px;&lt;br/&gt;        margin-bottom: 60px;&lt;br/&gt;      }&lt;br/&gt;      .feature {&lt;br/&gt;        background: #1a1a1a;&lt;br/&gt;        padding: 30px;&lt;br/&gt;        border-radius: 12px;&lt;br/&gt;        border: 1px solid #333;&lt;br/&gt;        transition: transform 0.3s ease, border-color 0.3s ease;&lt;br/&gt;      }&lt;br/&gt;      .feature:hover {&lt;br/&gt;        transform: translateY(-5px);&lt;br/&gt;        border-color: #555;&lt;br/&gt;      }&lt;br/&gt;      .feature h3 {&lt;br/&gt;        font-size: 1.3rem;&lt;br/&gt;        margin-bottom: 15px;&lt;br/&gt;        color: #ffffff;&lt;br/&gt;        display: flex;&lt;br/&gt;        align-items: center;&lt;br/&gt;        gap: 10px;&lt;br/&gt;      }&lt;br/&gt;      .feature-icon {&lt;br/&gt;        font-size: 1.5rem;&lt;br/&gt;      }&lt;br/&gt;      .feature p {&lt;br/&gt;        color: #cccccc;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        text-align: left;&lt;br/&gt;      }&lt;br/&gt;      .feature-list {&lt;br/&gt;        background: #111;&lt;br/&gt;        padding: 40px;&lt;br/&gt;        border-radius: 12px;&lt;br/&gt;        border: 1px solid #333;&lt;br/&gt;        margin-bottom: 60px;&lt;br/&gt;      }&lt;br/&gt;      .feature-grid {&lt;br/&gt;        display: grid;&lt;br/&gt;        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));&lt;br/&gt;        gap: 20px;&lt;br/&gt;        margin-top: 20px;&lt;br/&gt;      }&lt;br/&gt;      .feature-column ul {&lt;br/&gt;        list-style: none;&lt;br/&gt;        padding: 0;&lt;br/&gt;      }&lt;br/&gt;      .feature-column li {&lt;br/&gt;        display: flex;&lt;br/&gt;        align-items: center;&lt;br/&gt;        margin-bottom: 12px;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        padding: 8px 0;&lt;br/&gt;      }&lt;br/&gt;      .feature-check {&lt;br/&gt;        display: inline-block;&lt;br/&gt;        width: 20px;&lt;br/&gt;        height: 20px;&lt;br/&gt;        margin-right: 12px;&lt;br/&gt;        border-radius: 3px;&lt;br/&gt;        text-align: center;&lt;br/&gt;        line-height: 20px;&lt;br/&gt;        font-size: 12px;&lt;br/&gt;        font-weight: bold;&lt;br/&gt;        flex-shrink: 0;&lt;br/&gt;      }&lt;br/&gt;      .feature-check.completed {&lt;br/&gt;        background: #22c55e;&lt;br/&gt;        color: #ffffff;&lt;br/&gt;      }&lt;br/&gt;      .feature-check.pending {&lt;br/&gt;        background: #374151;&lt;br/&gt;        color: #9ca3af;&lt;br/&gt;        border: 1px solid #4b5563;&lt;br/&gt;      }&lt;br/&gt;      .feature-check.completed::after {&lt;br/&gt;        content: &amp;#34;✓&amp;#34;;&lt;br/&gt;      }&lt;br/&gt;      .feature-check.pending::after {&lt;br/&gt;        content: &amp;#34;○&amp;#34;;&lt;br/&gt;      }&lt;br/&gt;      .feature-text {&lt;br/&gt;        color: #e0e0e0;&lt;br/&gt;      }&lt;br/&gt;      .feature-text.pending {&lt;br/&gt;        color: #9ca3af;&lt;br/&gt;      }&lt;br/&gt;      .install-section {&lt;br/&gt;        background: #111;&lt;br/&gt;        padding: 40px;&lt;br/&gt;        border-radius: 12px;&lt;br/&gt;        border: 1px solid #333;&lt;br/&gt;        margin-bottom: 60px;&lt;br/&gt;      }&lt;br/&gt;      .install-section h2 {&lt;br/&gt;        margin-bottom: 20px;&lt;br/&gt;        text-align: center;&lt;br/&gt;      }&lt;br/&gt;      .code-block {&lt;br/&gt;        background: #000;&lt;br/&gt;        padding: 20px;&lt;br/&gt;        border-radius: 8px;&lt;br/&gt;        font-family: &amp;#34;Courier New&amp;#34;, monospace;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        border: 1px solid #333;&lt;br/&gt;        overflow-x: auto;&lt;br/&gt;        margin: 15px 0;&lt;br/&gt;      }&lt;br/&gt;      .code-block p {&lt;br/&gt;        color: #00ff00;&lt;br/&gt;      }&lt;br/&gt;      .code-block p::before {&lt;br/&gt;        content: &amp;#34;$ &amp;#34;;&lt;br/&gt;        color: #888;&lt;br/&gt;      }&lt;br/&gt;      .code-block span::before {&lt;br/&gt;        content: &amp;#34;$ &amp;#34;;&lt;br/&gt;        color: #888;&lt;br/&gt;      }&lt;br/&gt;      .code-block span {&lt;br/&gt;        color: #777;&lt;br/&gt;      }&lt;br/&gt;      .code-block pre {&lt;br/&gt;        color: #00ff00;&lt;br/&gt;      }&lt;br/&gt;      .links {&lt;br/&gt;        margin-top: 30px;&lt;br/&gt;      }&lt;br/&gt;      .links h3 {&lt;br/&gt;        font-size: 1.3rem;&lt;br/&gt;        margin-bottom: 15px;&lt;br/&gt;        color: #ffffff;&lt;br/&gt;      }&lt;br/&gt;      .links ul {&lt;br/&gt;        list-style: none;&lt;br/&gt;      }&lt;br/&gt;      .links li {&lt;br/&gt;        margin-bottom: 8px;&lt;br/&gt;      }&lt;br/&gt;      .links a {&lt;br/&gt;        color: #cccccc;&lt;br/&gt;        text-decoration: none;&lt;br/&gt;        font-size: 1rem;&lt;br/&gt;        transition: color 0.3s ease;&lt;br/&gt;      }&lt;br/&gt;      .links a:hover {&lt;br/&gt;        color: #ffffff;&lt;br/&gt;      }&lt;br/&gt;      .links a:before {&lt;br/&gt;        content: &amp;#34;→ &amp;#34;;&lt;br/&gt;        margin-right: 8px;&lt;br/&gt;      }&lt;br/&gt;      .status-badge {&lt;br/&gt;        display: inline-block;&lt;br/&gt;        padding: 4px 12px;&lt;br/&gt;        background: #1a4d1a;&lt;br/&gt;        color: #4ade80;&lt;br/&gt;        border-radius: 16px;&lt;br/&gt;        font-size: 0.8rem;&lt;br/&gt;        font-weight: 500;&lt;br/&gt;        margin-bottom: 20px;&lt;br/&gt;      }&lt;br/&gt;      .footer {&lt;br/&gt;        text-align: center;&lt;br/&gt;        padding: 40px 0;&lt;br/&gt;        border-top: 1px solid #333;&lt;br/&gt;        margin-top: 80px;&lt;br/&gt;        color: #888;&lt;br/&gt;      }&lt;br/&gt;      @media (max-width: 768px) {&lt;br/&gt;        .hero h1 {&lt;br/&gt;          font-size: 2.5rem;&lt;br/&gt;        }&lt;br/&gt;        .hero p {&lt;br/&gt;          font-size: 1.1rem;&lt;br/&gt;        }&lt;br/&gt;        .buttons {&lt;br/&gt;          flex-direction: column;&lt;br/&gt;          align-items: center;&lt;br/&gt;        }&lt;br/&gt;        .btn {&lt;br/&gt;          width: 200px;&lt;br/&gt;        }&lt;br/&gt;        .section h2 {&lt;br/&gt;          font-size: 1.8rem;&lt;br/&gt;        }&lt;br/&gt;        .features {&lt;br/&gt;          grid-template-columns: 1fr;&lt;br/&gt;        }&lt;br/&gt;        .feature-grid {&lt;br/&gt;          grid-template-columns: 1fr;&lt;br/&gt;        }&lt;br/&gt;        .install-section,&lt;br/&gt;        .feature-list {&lt;br/&gt;          padding: 20px;&lt;br/&gt;        }&lt;br/&gt;      }&lt;br/&gt;    &amp;lt;/style&amp;gt;&lt;br/&gt;  &amp;lt;/head&amp;gt;&lt;br/&gt;  &amp;lt;body&amp;gt;&lt;br/&gt;    &amp;lt;div class=&amp;#34;container&amp;#34;&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;hero&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;status-badge&amp;#34;&amp;gt;✨ Looking for feedback&amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;h1&amp;gt;n34&amp;lt;/h1&amp;gt;&lt;br/&gt;        &amp;lt;p&amp;gt;&lt;br/&gt;          n34 is an open source command-line interface (CLI) tool for&lt;br/&gt;          sending and receiving Git issues, patches and comments over the&lt;br/&gt;          Nostr protocol. It supports creating, replying to, and managing&lt;br/&gt;          issues and patches, making Git collaboration decentralized and&lt;br/&gt;          censorship-resistant.&lt;br/&gt;        &amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;buttons&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;a href=&amp;#34;&lt;a href=&#34;https://n34.dev/commands.html&amp;#34&#34;&gt;https://n34.dev/commands.html&amp;#34&lt;/a&gt;; class=&amp;#34;btn&amp;#34;&amp;gt;Documentation&amp;lt;/a&amp;gt;&lt;br/&gt;          &amp;lt;a href=&amp;#34;&lt;a href=&#34;https://git.4rs.nl/awiteb/n34.git&amp;#34&#34;&gt;https://git.4rs.nl/awiteb/n34.git&amp;#34&lt;/a&gt;; class=&amp;#34;btn&amp;#34;&amp;gt;Git Repository&amp;lt;/a&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;section&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;h2&amp;gt;Key Features&amp;lt;/h2&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;features&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;h3&amp;gt;&amp;lt;span class=&amp;#34;feature-icon&amp;#34;&amp;gt;🔄&amp;lt;/span&amp;gt;Complete Git Workflow&amp;lt;/h3&amp;gt;&lt;br/&gt;            &amp;lt;p&amp;gt;Handle the full development lifecycle with patches, issues, replies, and status tracking, all through the decentralized Nostr protocol.&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;h3&amp;gt;&amp;lt;span class=&amp;#34;feature-icon&amp;#34;&amp;gt;🔗&amp;lt;/span&amp;gt;&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/34.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/34.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-34&amp;lt;/a&amp;gt; Compliant&amp;lt;/h3&amp;gt;&lt;br/&gt;            &amp;lt;p&amp;gt;Fully implements the &amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/34.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/34.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-34&amp;lt;/a&amp;gt; specification for Git repositories on Nostr, ensuring compatibility with the decentralized ecosystem.&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;h3&amp;gt;&amp;lt;span class=&amp;#34;feature-icon&amp;#34;&amp;gt;🛠️&amp;lt;/span&amp;gt;Developer Friendly&amp;lt;/h3&amp;gt;&lt;br/&gt;            &amp;lt;p&amp;gt;Intuitive CLI interface designed for developers who want to integrate Git workflows with Nostr seamlessly.&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;h3&amp;gt;&amp;lt;span class=&amp;#34;feature-icon&amp;#34;&amp;gt;🔐&amp;lt;/span&amp;gt;Self-Sovereign&amp;lt;/h3&amp;gt;&lt;br/&gt;            &amp;lt;p&amp;gt;No accounts, no passwords, no centralized servers. You control your identity and data through cryptographic keys.&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;feature-list&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;h2&amp;gt;Feature Roadmap&amp;lt;/h2&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin-bottom: 30px; color: #cccccc;&amp;#34;&amp;gt;Current implementation status and upcoming features&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;feature-grid&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature-column&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;ul&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Repository announcements&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check pending&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text pending&amp;#34;&amp;gt;Repository state announcements&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Patches (Send, fetch and list)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Issues (Send, view and list)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Replies&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Issues and patches status&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check pending&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text pending&amp;#34;&amp;gt;Pull requests (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/pull/1966&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/pull/1966&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;nostr-protocol/nips#1966&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;            &amp;lt;/ul&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;          &amp;lt;div class=&amp;#34;feature-column&amp;#34;&amp;gt;&lt;br/&gt;            &amp;lt;ul&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Gossip Model (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/65.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/65.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-65&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Proof of Work (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/13.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/13.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-13&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;nostr: URI scheme (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/21.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/21.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-21&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Signing using bunker (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/46.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/46.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-46&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text pending&amp;#34;&amp;gt;Signing using &amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/07.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/07.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-07&amp;lt;/a&amp;gt; proxy (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://crates.io/crates/nostr-browser-signer-proxy&amp;#34&#34;&gt;https://crates.io/crates/nostr-browser-signer-proxy&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;nostr-browser-signer-proxy&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;Secret key keyring&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check pending&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text pending&amp;#34;&amp;gt;Code Snippets (&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/C0.md&amp;#34&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/C0.md&amp;#34&lt;/a&gt;; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noreferrer&amp;#34;&amp;gt;NIP-C0&amp;lt;/a&amp;gt;)&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;              &amp;lt;li&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-check completed&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&lt;br/&gt;                &amp;lt;span class=&amp;#34;feature-text&amp;#34;&amp;gt;In device relays and repos bookmark&amp;lt;/span&amp;gt;&lt;br/&gt;              &amp;lt;/li&amp;gt;&lt;br/&gt;            &amp;lt;/ul&amp;gt;&lt;br/&gt;          &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;install-section&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;h2&amp;gt;Quick Start&amp;lt;/h2&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin-bottom: 20px; color: #cccccc;&amp;#34;&amp;gt;Get started with n34 in seconds&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;code-block&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;p&amp;gt;cargo install n34&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;span&amp;gt;# The execautable will be in ~/.cargo/bin/n34&amp;lt;/span&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin: 20px 0; color: #888;&amp;#34;&amp;gt;or&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;code-block&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;p&amp;gt;git clone &lt;a href=&#34;https://git.4rs.nl/awiteb/n34.git&amp;lt;/p&amp;gt&#34;&gt;https://git.4rs.nl/awiteb/n34.git&amp;lt;/p&amp;gt&lt;/a&gt;;&lt;br/&gt;          &amp;lt;p&amp;gt;cd n34 &amp;amp;&amp;amp; cargo build --release&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;span&amp;gt;# The execautable will be in target/release/n34&amp;lt;/span&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin: 20px 0; color: #888;&amp;#34;&amp;gt;NixOS (Version 0.4.0 or later)&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;code-block&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;p&amp;gt;git clone &lt;a href=&#34;https://git.4rs.nl/awiteb/n34.git&amp;lt;/p&amp;gt&#34;&gt;https://git.4rs.nl/awiteb/n34.git&amp;lt;/p&amp;gt&lt;/a&gt;;&lt;br/&gt;          &amp;lt;p&amp;gt;cd n34 &amp;amp;&amp;amp; nix build&amp;lt;/p&amp;gt;&lt;br/&gt;          &amp;lt;span&amp;gt;# The execautable will be in result/bin/n34&amp;lt;/span&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin: 20px 0; color: #888;&amp;#34;&amp;gt;home-manager&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin-bottom: 20px; color: #cccccc;&amp;#34;&amp;gt;Add this to your &amp;lt;code&amp;gt;flake.nix&amp;lt;/code&amp;gt; inputs&lt;br/&gt;        &amp;lt;br&amp;gt;&lt;br/&gt;Specify the version you want to install, or remove &amp;lt;code&amp;gt;?ref&amp;lt;/code&amp;gt; for the unreleased version. You can also use any mirror; it doesn&amp;#39;t have to be &amp;lt;code&amp;gt;git.4rs.nl&amp;lt;/code&amp;gt;&lt;br/&gt;        &amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;code-block&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;pre&amp;gt;&lt;br/&gt;inputs = {&lt;br/&gt;  n34.url = &amp;#34;git&#43;&lt;a href=&#34;https://git.4rs.nl/awiteb/n34.git?ref=refs/tags/vx.y.x&amp;#34&#34;&gt;https://git.4rs.nl/awiteb/n34.git?ref=refs/tags/vx.y.x&amp;#34&lt;/a&gt;;;&lt;br/&gt;};&amp;lt;/pre&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin-bottom: 20px; color: #cccccc;&amp;#34;&amp;gt;And this in your home packages&amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;code-block&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;pre&amp;gt;packages = [ inputs.n34.packages.&amp;#34;${pkgs.system}&amp;#34;.default ];&amp;lt;/pre&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;        &amp;lt;p style=&amp;#34;text-align: center; margin-top: 20px; color: #cccccc;&amp;#34;&amp;gt;Once installed, run &amp;lt;code style=&amp;#34;background: #333; padding: 2px 6px; border-radius: 4px;&amp;#34;&amp;gt;n34 --help&amp;lt;/code&amp;gt; to see available commands.&amp;lt;/p&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;section&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;h2&amp;gt;Why Nostr?&amp;lt;/h2&amp;gt;&lt;br/&gt;        &amp;lt;p&amp;gt;&lt;br/&gt;          Nostr is fundamentally different from traditional platforms because it&amp;#39;s not an application or service, it&amp;#39;s a decentralized protocol. This means any tool or app can integrate with it, enabling open, permissionless collaboration&lt;br/&gt;          without relying on centralized gatekeepers. Unlike proprietary systems, Nostr doesn&amp;#39;t require emails, passwords, or accounts. You interact directly through relays, whether you self-host your own or use public ones, ensuring no&lt;br/&gt;          single point of failure or control.&lt;br/&gt;        &amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;p&amp;gt;&lt;br/&gt;          What makes Nostr uniquely resilient is its design, the protocol itself is just a set of rules, not a company or product that can disappear. Your Git issues, patches, and comments persist as long as relays choose to store them,&lt;br/&gt;          immune to the whims of corporate shutdowns or policy changes. Nostr is infrastructure in its purest form, an idea that outlives any temporary implementation. n34 taps into a future-proof foundation for decentralized collaboration.&lt;br/&gt;        &amp;lt;/p&amp;gt;&lt;br/&gt;        &amp;lt;div class=&amp;#34;links&amp;#34;&amp;gt;&lt;br/&gt;          &amp;lt;h3&amp;gt;More about Nostr&amp;lt;/h3&amp;gt;&lt;br/&gt;          &amp;lt;ul&amp;gt;&lt;br/&gt;            &amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://nostr.com&amp;#34;&amp;gt;nostr.com&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&#34;&gt;https://nostr.com&amp;#34;&amp;gt;nostr.com&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&lt;/a&gt;;&lt;br/&gt;            &amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://nostr.org&amp;#34;&amp;gt;nostr.org&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&#34;&gt;https://nostr.org&amp;#34;&amp;gt;nostr.org&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&lt;/a&gt;;&lt;br/&gt;            &amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;&lt;a href=&#34;https://nostr.how/en/what-is-nostr&amp;#34;&amp;gt;nostr.how/en/what-is-nostr&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&#34;&gt;https://nostr.how/en/what-is-nostr&amp;#34;&amp;gt;nostr.how/en/what-is-nostr&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt&lt;/a&gt;;&lt;br/&gt;          &amp;lt;/ul&amp;gt;&lt;br/&gt;        &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;      &amp;lt;div class=&amp;#34;footer&amp;#34;&amp;gt;&lt;br/&gt;        &amp;lt;p&amp;gt;Built with ❤️ for the decentralized future&amp;lt;/p&amp;gt;&lt;br/&gt;      &amp;lt;/div&amp;gt;&lt;br/&gt;    &amp;lt;/div&amp;gt;&lt;br/&gt;  &amp;lt;/body&amp;gt;&lt;br/&gt;&amp;lt;/html&amp;gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:24&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsw3ukfu5c3lvhym40ueyscp93qsl2gah77rkxv2ltk9hjy7espuxszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfh495m</id>
    
      <title type="html"># Fallback Relays &amp;gt; `n34 config relays` command **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsw3ukfu5c3lvhym40ueyscp93qsl2gah77rkxv2ltk9hjy7espuxszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsfh495m" />
    <content type="html">
      # Fallback Relays&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 config relays` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Sets the default fallback relays if none provided. Use this relays for read and write&lt;br/&gt;&lt;br/&gt;Usage: n34 config relays [OPTIONS] [RELAYS]...&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [RELAYS]...  List of relay URLs to append to fallback relays. If empty, removes all fallback relays&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --override  Replace existing fallback relays instead of appending new ones&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;This command configures the default fallback relays, which `n34` uses to read&lt;br/&gt;from and write to. To add relays, provide their URLs as arguments to append&lt;br/&gt;them to the current list. Use the `--override` flag to replace the existing list&lt;br/&gt;entirely. To clear all fallback relays, run the command without any arguments.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:13&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs0nlfjscjh4p6rsnfh2lqss4c0eqh6eqn6xjtpsg9wuqatpfgyzxszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsff30a4</id>
    
      <title type="html"># Default PoW Difficulty &amp;gt; `n34 config pow` **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs0nlfjscjh4p6rsnfh2lqss4c0eqh6eqn6xjtpsg9wuqatpfgyzxszyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsff30a4" />
    <content type="html">
      # Default PoW Difficulty&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 config pow`&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Sets the default PoW difficulty (0 if not specified)&lt;br/&gt;&lt;br/&gt;Usage: n34 config pow &amp;lt;DIFFICULTY&amp;gt;&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  &amp;lt;DIFFICULTY&amp;gt;  The new default PoW difficulty&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;This command configures the default Proof of Work (PoW) difficulty for newly&lt;br/&gt;created events. This setting is applied to most generated events, but it&lt;br/&gt;intentionally skips patch events. Because patches can be numerous, calculating&lt;br/&gt;PoW for each one would significantly slow down operations.&lt;br/&gt;&lt;br/&gt;If you want to disable the PoW just make it 0.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:41:02&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsv4wserm8938w3rz6zhsaj75fh3ktcvv6sj3nqzz4qx74s5pkwylczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsexd283</id>
    
      <title type="html"># NIP-07 Browser Signer Proxy &amp;gt; `n34 config nip07` **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsv4wserm8938w3rz6zhsaj75fh3ktcvv6sj3nqzz4qx74s5pkwylczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsexd283" />
    <content type="html">
      # NIP-07 Browser Signer Proxy&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 config nip07`&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Manage the NIP-07 browser signer proxy by enabling or disabling it and configuring the `ip:port` address.&lt;br/&gt;&lt;br/&gt;Usage: n34 config nip07 [OPTIONS] &amp;lt;--enable|--disable&amp;gt;&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --enable       Enable NIP-07 as the default signer&lt;br/&gt;      --disable      Disable NIP-07 as the default signer&lt;br/&gt;      --addr &amp;lt;ADDR&amp;gt;  Set the `ip:port` for the browser signer proxy (default: 127.0.0.1:51034)&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Use [NIP-07] (Browser Extension Signer) as your default signer. This is achieved&lt;br/&gt;by running a proxy at the specified `ADDR`, which defaults to `127.0.0.1:51034`.&lt;br/&gt;The proxy forwards `n34` requests to the browser signer and relays the responses&lt;br/&gt;back.&lt;br/&gt;&lt;br/&gt;[NIP-07]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/07.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/07.md&lt;/a&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:40:51&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsy63ttv5r4d45x8yf5g7v49r6dhseqjgd42t350agg8kzamxpuv8qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsanfqnp</id>
    
      <title type="html"># Secret Key Keyring &amp;gt; `n34 config keyring` command **Usage:** ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsy63ttv5r4d45x8yf5g7v49r6dhseqjgd42t350agg8kzamxpuv8qzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsanfqnp" />
    <content type="html">
      # Secret Key Keyring&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 config keyring` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Manages the secret key keyring, including enabling, disabling, or resetting it.&lt;br/&gt;&lt;br/&gt;Usage: n34 config keyring &amp;lt;--enable|--disable|--reset&amp;gt;&lt;br/&gt;&lt;br/&gt;Options:&lt;br/&gt;      --enable   Enables the secret key keyring. You will be prompted for your key one last time to store it.&lt;br/&gt;      --disable  Disables the secret key keyring. This removes the stored key and prevents new ones from being saved.&lt;br/&gt;      --reset    Resets the keyring. This deletes the current key, allowing a new one to be stored on the next use.&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;To avoid entering your private key for every command, you can enable the keyring&lt;br/&gt;to store it securely. First, run `n34 config keyring --enable`. The next time&lt;br/&gt;you run an `n34` command that requires your private key, it will be saved&lt;br/&gt;to your system&amp;#39;s keyring. You will not need to enter it again for subsequent&lt;br/&gt;commands.&lt;br/&gt;&lt;br/&gt;To replace the stored key with a new one, use the `--reset` flag. To stop using&lt;br/&gt;the keyring and remove the stored key, use the `--disable` flag.&lt;br/&gt;&lt;br/&gt;`n34` uses your operating system&amp;#39;s native secret management system. For example,&lt;br/&gt;it uses `keyutils` on Linux and `Keychain` on macOS.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:40:40&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsvlgydzu06euuq2qgnasxgx34yjqdhx7uux8yq27zkspdzrrmvhyqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs5hly5p</id>
    
      <title type="html"># NIP-46 Bunker &amp;gt; `n34 config bunker` command **Usage:** ``` ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsvlgydzu06euuq2qgnasxgx34yjqdhx7uux8yq27zkspdzrrmvhyqzyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrs5hly5p" />
    <content type="html">
      # NIP-46 Bunker&lt;br/&gt;&lt;br/&gt;&amp;gt; `n34 config bunker` command&lt;br/&gt;&lt;br/&gt;**Usage:**&lt;br/&gt;```&lt;br/&gt;Sets a URL of NIP-46 bunker server used for signing events&lt;br/&gt;&lt;br/&gt;Usage: n34 config bunker [BUNKER_URL]&lt;br/&gt;&lt;br/&gt;Arguments:&lt;br/&gt;  [BUNKER_URL]  Nostr Connect URL for the bunker. Omit this to remove the current bunker URL&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;This command configures `n34` to use a remote signer ([NIP-46]), known as a&lt;br/&gt;bunker, for all cryptographic operations.&lt;br/&gt;&lt;br/&gt;When `n34` communicates with the bunker, it uses a persistent, locally-generated&lt;br/&gt;keypair. You should add this keypair&amp;#39;s public key to your bunker&amp;#39;s list of&lt;br/&gt;authorized applications. This allows `n34` to operate securely without needing&lt;br/&gt;direct access to your main private key.&lt;br/&gt;&lt;br/&gt;Once configured, actions such as fetching your public key or signing events are&lt;br/&gt;delegated to the bunker. To remove the bunker configuration, run the command&lt;br/&gt;again without providing a URL.&lt;br/&gt;&lt;br/&gt;[NIP-46]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/46.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/46.md&lt;/a&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:40:30&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsretnn7u9wpujltg6qx8j0kw2d5xrueaxdvjnclvp7wwlstas9f0czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsx6vxxp</id>
    
      <title type="html"># Manage Configuration Configuration allows you to set default ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsretnn7u9wpujltg6qx8j0kw2d5xrueaxdvjnclvp7wwlstas9f0czyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsx6vxxp" />
    <content type="html">
      # Manage Configuration&lt;br/&gt;&lt;br/&gt;Configuration allows you to set default values for various command parameters,&lt;br/&gt;such as fallback relays, Proof of Work (PoW) difficulty, a default bunker URL,&lt;br/&gt;and your Nostr private key (keyring). This avoids the need to enter your private key,&lt;br/&gt;bunker URL, relays, or PoW difficulty for every command, making `n34` more&lt;br/&gt;convenient to use.&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:40:19&#43;02:00</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8u7nhte9q8c33xx889mqcnnfcc5gk9nd7ujg3rsjdneczsk2q0mczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsv5mugd</id>
    
      <title type="html"># Command-Line Usage ## Options The `n34` command-line tool ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8u7nhte9q8c33xx889mqcnnfcc5gk9nd7ujg3rsjdneczsk2q0mczyqm8tn4l98txa6ufsnpl22wn05jtzk7c2lqypjsy7sl7yuh6q6xrsv5mugd" />
    <content type="html">
      # Command-Line Usage&lt;br/&gt;&lt;br/&gt;## Options&lt;br/&gt;&lt;br/&gt;The `n34` command-line tool accepts the following options:&lt;br/&gt;&lt;br/&gt;-   `-s`, `--secret-key`: Your Nostr secret key (in `nsec` format), used for&lt;br/&gt;  signing events.&lt;br/&gt;-   `-b`, `--bunker-url`: The URL of a NIP-46 bunker service used for remote&lt;br/&gt;  signing of events.&lt;br/&gt;-   `-7`, `--nip07`: Enables signing events using the browser&amp;#39;s NIP-07&lt;br/&gt;  extension. Listens on `127.0.0.1:51034`. You can configure the address with `n34&lt;br/&gt;  config nip07`&lt;br/&gt;-   `-r`, `--relays`: A relay to read from and write to. This option can be&lt;br/&gt;  specified multiple times to connect to several relays.&lt;br/&gt;-   `--pow`: Sets the Proof of Work difficulty required when creating events.&lt;br/&gt;-   `--config`: Specifies a custom path to the configuration file (Default:&lt;br/&gt;  `$HOME/.config/n34/config.toml`).&lt;br/&gt;-   `-v`, `--verbose...`: Increases the logging verbosity. Can be used multiple&lt;br/&gt;  times for more detail (e.g., `-v`, `-vv`).&lt;br/&gt;&lt;br/&gt;**Note:** The `--secret-key` and `--bunker-url` options are mutually exclusive.&lt;br/&gt;You must provide exactly one signing method.&lt;br/&gt;&lt;br/&gt;## Multiple Repositories&lt;br/&gt;&lt;br/&gt;Commands that interact with a repository, such as submitting an issue or a&lt;br/&gt;patch, can accept multiple repository addresses (`naddr`). This feature is&lt;br/&gt;useful for projects with multiple maintainers who each have their own repository&lt;br/&gt;fork.&lt;br/&gt;&lt;br/&gt;&amp;gt; **Important:** When you provide multiple repositories, `n34` does not&lt;br/&gt;create a separate issue or patch for each one. Instead, it creates a single&lt;br/&gt;event that references all of the specified repositories.&lt;br/&gt;&lt;br/&gt;## The `nostr-address` File&lt;br/&gt;&lt;br/&gt;The `nostr-address` file is a plain text file that stores a list of project&lt;br/&gt;repository addresses. This allows the `n34` to find and use them&lt;br/&gt;without requiring you to enter the addresses manually.&lt;br/&gt;&lt;br/&gt;### Format&lt;br/&gt;&lt;br/&gt;- Each line must contain a single addressable event coordinate `naddr` which is&lt;br/&gt;  the repository address.&lt;br/&gt;- Lines beginning with a `#` are treated as comments and are ignored.&lt;br/&gt;- Empty lines are also ignored.&lt;br/&gt;&lt;br/&gt;## Passing repositories&lt;br/&gt;&lt;br/&gt;By default, `n34` will look for a `nostr-address` file to extract repositories&lt;br/&gt;from it. This is why repositories are not required for commands like `patch&lt;br/&gt;send` and `issue new`. You can also pass repositories using the `--repo`&lt;br/&gt;option or the `&amp;lt;NADDR-NIP05-OR-SET&amp;gt;` argument for commands that accept them. The&lt;br/&gt;supported formats for manual input are:&lt;br/&gt;&lt;br/&gt;- A [NIP-19] addressable event coordinate `naddr`.&lt;br/&gt;- A [NIP-05] identifier and repository name, in the format&lt;br/&gt;  `&amp;lt;nip05&amp;gt;/&amp;lt;repo-name&amp;gt;`.&lt;br/&gt;- A set name that contains repository addresses.&lt;br/&gt;&lt;br/&gt;You do not need to specify relays for these commands if your `naddr` or `NIP-05`&lt;br/&gt;identifier already includes relays; `n34` will automatically extract them.&lt;br/&gt;&lt;br/&gt;[NIP-19]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/19.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/19.md&lt;/a&gt;&lt;br/&gt;[NIP-05]: &lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/05.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/05.md&lt;/a&gt;&lt;br/&gt;
    </content>
    <updated>2026-04-05T02:40:08&#43;02:00</updated>
  </entry>

</feed>