<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bjarne Rentz</title>
    <description>The latest articles on DEV Community by Bjarne Rentz (@bjarnerentz).</description>
    <link>https://dev.to/bjarnerentz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1173166%2F8d9eb8f1-9dd8-4efa-bbfc-3e8c1e0974e5.jpeg</url>
      <title>DEV Community: Bjarne Rentz</title>
      <link>https://dev.to/bjarnerentz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bjarnerentz"/>
    <language>en</language>
    <item>
      <title>Configure DependencyTrack with Terraform</title>
      <dc:creator>Bjarne Rentz</dc:creator>
      <pubDate>Sun, 15 Feb 2026 14:49:24 +0000</pubDate>
      <link>https://dev.to/bjarnerentz/configure-dependencytrack-with-terraform-32ga</link>
      <guid>https://dev.to/bjarnerentz/configure-dependencytrack-with-terraform-32ga</guid>
      <description>&lt;p&gt;Dependency management and Software Bill of Materials (SBOMs) are more important than ever. With the upcoming EU Cyber Resilience Act, they are becoming a legal mandate for many organizations. Even beyond compliance, using SBOMs in combination with reproducible builds as your "source of truth" for dependencies is a good security practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dependencytrack.org" rel="noopener noreferrer"&gt;DependencyTrack&lt;/a&gt; is a great tool from OWASP for managing SBOMs and auditing vulnerabilities of used dependencies. DependencyTrack is built around projects. While a project type (e.g., Application, Container Image) is technically just a label, how you structure them matters.&lt;/p&gt;

&lt;p&gt;I recommend creating a standalone project for each component of your application (e.g., separate projects for your frontend and backend). This separation provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Auditing:&lt;/strong&gt; Vulnerabilities are easier to triage when you know exactly which layer they affect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistical Transparency:&lt;/strong&gt; Clearer metrics on component counts per service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical Organization:&lt;/strong&gt; Better integration with &lt;em&gt;Collection Projects&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collection Projects allow you to organize projects in a tree based structure. As an example, for an app &lt;em&gt;My Calendar&lt;/em&gt; you can create a parent collection project that aggregates data from its child projects (Frontend and Backend). These collection project can't contain any direct components but rather aggregate them from their child projects dependent on the configuration.&lt;/p&gt;

&lt;p&gt;As you probably already guessed, they are great to structure multiple apps, containing multiple components deployed across different environments such as dev or prod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- My-Shop
  - Basket Service
    - Basket Service (Dev)
      - Basket Service Frontend (Dev)
      - Basket Service Backend (Prod)
    - Basket Service (Prod)
      - Baskend Service Frontend (Dev)
      - Basket Service Frontend (Prod)
  - Payment Service
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main hurdle to this granular approach is the manual effort required to set up dozens of nested projects. This is where the &lt;a href="https://registry.terraform.io/providers/SolarFactories/dependencytrack/latest/docs" rel="noopener noreferrer"&gt;DependencyTrack Terraform Provider&lt;/a&gt; becomes essential.&lt;/p&gt;

&lt;p&gt;The provider automates the creation of nested structures, teams, permissions, and LDAP mappings. If you want to dive straight into a "Product Group" approach - grouping services like Payment, Basket, and Inventory into a single functional unit - I’ve developed a &lt;a href="https://github.com/BjarneRentz/terraform-dependencytrack-project-setup" rel="noopener noreferrer"&gt;Terraform Module&lt;/a&gt; to handle the heavy lifting for you.&lt;/p&gt;

&lt;p&gt;Even if you don't use my module, you should treat your DependencyTrack configuration as code. Moving away from manual clicks ensures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auditability:&lt;/strong&gt; Every change is tracked in version control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disaster Recovery:&lt;/strong&gt; Easily restore your entire project structure if a database is lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Living Documentation:&lt;/strong&gt; Your HCL files explain the setup better than a "lost" colleague ever could.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>terraform</category>
      <category>dependencytrack</category>
      <category>security</category>
    </item>
    <item>
      <title>Journey Developing an Obsidian Plugin Part 2 - Improving the Architecture, Basic Error Handling and more!</title>
      <dc:creator>Bjarne Rentz</dc:creator>
      <pubDate>Mon, 06 Jan 2025 18:44:49 +0000</pubDate>
      <link>https://dev.to/bjarnerentz/journey-developing-an-obsidian-plugin-part-2-improving-the-architecture-basic-error-handling-and-5aa6</link>
      <guid>https://dev.to/bjarnerentz/journey-developing-an-obsidian-plugin-part-2-improving-the-architecture-basic-error-handling-and-5aa6</guid>
      <description>&lt;p&gt;Welcome back to the second part! &lt;a href="https://dev.to/bjarnerentz/journey-developing-an-obsidian-plugin-part-1-getting-started-53m6"&gt;In the first part&lt;/a&gt;, we explored the basics of creating an Obsidian plugin. If you missed it, I recommend reading it first. Now we'll improve the overall architecture and file layout of the plugin as well as adding basic error handling. Furthermore, I share my learnings from releasing the first patch versions, lets get started!&lt;/p&gt;

&lt;h1&gt;
  
  
  The Starting Point
&lt;/h1&gt;

&lt;p&gt;Starting from the mentioned plugin-template, the overall structure of the plugin are a bunch of files all within the root folder of the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main.ts
styles.css
manifest.json
versions.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While not displaying technical configurations files such as &lt;code&gt;package.json&lt;/code&gt;. The plugins's entire source code, including its settings, UI and logic is contained in a single file, &lt;code&gt;main.ts&lt;/code&gt;. While serving the point of a starting point and template, this does not represent a good structure for further feature development.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Desired State
&lt;/h1&gt;

&lt;p&gt;Before describing the current layout and architecture, let's first recap some general guidelines used in this process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start simple and improve / reconcile as you grow: You don't win anything, if you start your simple project with the ultimate, scalable architecture. &lt;strong&gt;Start simple with your current requirements while being open for changes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Separate different tasks into different files. We want to get rid of a single &lt;code&gt;main.ts&lt;/code&gt;, that is responsible for all aspects of the plugin.
With that done, I first started to separate all the classes within the &lt;code&gt;main.ts&lt;/code&gt; into their own files and moved all of them into a &lt;code&gt;src&lt;/code&gt; directory yielding:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src
|   commands.ts
|   gemini-client.ts
|   main.ts
|   settings-tab.ts
|   settings.ts
package.json
package-lock.jsoon
README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main idea behind this structure is the separation of different tasks of the plugin into their own files: &lt;code&gt;settings-tab.ts&lt;/code&gt; and &lt;code&gt;settings.ts&lt;/code&gt; defines the UI and data structures for the required API-Key settings.&lt;br&gt;
The single command of this plugin (with room to add more) is defined in &lt;code&gt;commands.ts&lt;/code&gt; and &lt;code&gt;gemini-client.ts&lt;/code&gt; abstracts the official Gemini library for the specific use case of this plugin and is used by the command. All parts are wired together in &lt;code&gt;main.ts&lt;/code&gt; which bootstraps the plugin within the &lt;code&gt;onload&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GeminiGenerator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeminiGeneratorSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Loads the plugin.&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSettingTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GeminiGeneratorSettingTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEditorCommands&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;addEditorCommands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;getEditorCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast to the initial version, the &lt;code&gt;main.ts&lt;/code&gt; got rid of all dependencies of the command. It only handles the settings and registers all EditorCommands (line 10). This registration is implemented with a combination of &lt;strong&gt;factory functions&lt;/strong&gt; and &lt;strong&gt;dependency injection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quick recap, EditorCommands are registered in Obsidian though the call of &lt;code&gt;addCommand&lt;/code&gt;, providing an object with the commands configuration and the callback, which represents the commands implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;print-greeting-to-console&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Print greeting to console&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hey, you!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hence, we are only able to register one command at a time. This is the reason, the private method &lt;code&gt;addEditorCommands&lt;/code&gt; of &lt;code&gt;main.ts&lt;/code&gt; iterates over a list of commands, returned by &lt;code&gt;getEditorCommands&lt;/code&gt; and registers them one-by-one. As the commands implementation require a reference to the plugin respectively editor, &lt;code&gt;this&lt;/code&gt; is passed as an argument.&lt;/p&gt;

&lt;p&gt;The implementation is rather simple: The method simply returns a list of all (currently one) commands which are created through their corresponding &lt;strong&gt;factory function&lt;/strong&gt;, which intern construct and return the mentioned command object required by the Obsidian API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getEditorCommands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeminiGenerator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;buildGenerateNoteCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the addition of more commands, it could prove useful to create a new folder &lt;code&gt;Commands&lt;/code&gt;, where each command is defined within its own file.&lt;/p&gt;

&lt;p&gt;All in all, this structure offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clearer separation of concerns&lt;/li&gt;
&lt;li&gt;Improved scalability for future features&lt;/li&gt;
&lt;li&gt;Easier maintenance
# Basic Error Handling
The current error handling is minimal at most, but this series is about sharing my leanings, so here we go! The main error handling can be described by four lines:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;notice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ An error occured during the Google Gemini Request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, if any error occurs during the request to Google Gemini, a small error toast is shown. This served well for the beginning, yet this is not very precise for the user, as some prompts would benefit from a more differentiated error message.&lt;br&gt;
Hence, I would suggest you to think about feasible error cases and messages. Do not overstrain your users with technical messages, while displaying enough information enabling to distinguish an input error from a server error for example.&lt;/p&gt;

&lt;h1&gt;
  
  
  Learnings from Releasing the first Versions
&lt;/h1&gt;

&lt;p&gt;I use a small GitHub Action for bundling an creating a GitHub Release whenever I a git tag is pushed / created. The GitHub release is created as a draft, such that I'm able to adapt the release log or title. If you are using this setup as well, do not forget to publish this release, as otherwise no one will be able to update / download your plugin anymore. That's because in you repository, the &lt;code&gt;versions.json&lt;/code&gt; already contains you new version but as the GitHub release is still a draft, no one is able to download it.&lt;/p&gt;

&lt;p&gt;If you have the same setup as I do, I would recommend following this procedure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push a git tag for a new version&lt;/li&gt;
&lt;li&gt;Wait for the Action to finish&lt;/li&gt;
&lt;li&gt;Validate the created release draft&lt;/li&gt;
&lt;li&gt;Publish the release&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again, thanks for reading and following this small series. As always, if you have suggestions or feedback, I'd love to hear from you in the comments! I'm looking forward to having more time for further development of the plugin. In the next part, we will add some more settings and another command, so stay tuned!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Resources and Tips
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Take a look at other Obsidian plugins such as &lt;a href="https://github.com/platers/obsidian-linter" rel="noopener noreferrer"&gt;Obsidian Linter&lt;/a&gt;, they are all open source!&lt;/li&gt;
&lt;li&gt;Start simple, be open to change and adapt as you go&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BjarneRentz/obsidian-gemini-generator" rel="noopener noreferrer"&gt;Sourcecode of this plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>obsidian</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Journey: Developing an Obsidian Plugin Part 1 - Getting Started</title>
      <dc:creator>Bjarne Rentz</dc:creator>
      <pubDate>Tue, 06 Aug 2024 14:10:40 +0000</pubDate>
      <link>https://dev.to/bjarnerentz/journey-developing-an-obsidian-plugin-part-1-getting-started-53m6</link>
      <guid>https://dev.to/bjarnerentz/journey-developing-an-obsidian-plugin-part-1-getting-started-53m6</guid>
      <description>&lt;p&gt;First of all, to set the right expectations, this is not a tutorial on how to develop Obsidian plugins. It's just a write-up of my journey developing and publishing an Obsidian plugin. I will write about my problems and my lessons learned, but not a step-by-step tutorial. The first part we will cover the idea, getting started with the development and submitting the MVP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;As you may have probably noticed, everyone is talking about AI. So why not jump on the hype train as well? For university (and everything else) I use the fantastic note-taking tool Obsidian. Once in a lecture I asked myself, why don't use ChatGPT or Google Gemini for this? Sure, the details are probably pretty bad on some topics, but the overall structure is already there. So I started with Google Gemini and the simple prompt &lt;em&gt;Generate me an Obsidian Markdown Note on: &amp;gt;&amp;gt;NoteTitel&amp;lt;&amp;lt;&lt;/em&gt; and copied the result into Obsidian.&lt;/p&gt;

&lt;p&gt;Depending on the topic, the notes were acceptable and needed only minor changes. I asked myself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wouldn't it be easier to just create the note in Obsidian, set the title and with the help of a plugin and AI, use a command to generate the Note.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the simple, yet handy idea behind my plugin &lt;em&gt;Gemini Generator&lt;/em&gt;. For a better understanding the GIF shows the workflow with &lt;em&gt;Gemini Generator&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FBjarneRentz%2Fobsidian-gemini-generator%2Fcd008a0552d1697370f1154d5eed35e49cbf7d9b%2Fimages%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FBjarneRentz%2Fobsidian-gemini-generator%2Fcd008a0552d1697370f1154d5eed35e49cbf7d9b%2Fimages%2Fdemo.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the Project
&lt;/h2&gt;

&lt;p&gt;At first I was a bit overwhelmed not knowing where to start. However, although it only shows the basics and not many examples of plugin functionality, the &lt;a href="https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin" rel="noopener noreferrer"&gt;Obsidian tutorial&lt;/a&gt; and the &lt;a href="https://github.com/obsidianmd/obsidian-sample-plugin" rel="noopener noreferrer"&gt;sample plugin&lt;/a&gt;, which provides the basic structure of a plugin, are a good place to start. It's often easier to start with something simple and learn as you go, rather than planning and researching everything in advance. Sure, it's always important to plan a basic path for learning something new, but leave some room for learning along the way!&lt;/p&gt;

&lt;p&gt;Starting with forking the sample project and renaming all relevant class names, the first goals were to find the appropriate command type and how to get the title of the currently open note. The first task was easily solved with the &lt;code&gt;EditorCommand&lt;/code&gt;, as it provides an instance of the &lt;code&gt;Editor&lt;/code&gt; for writing the response and is only available when an Editor-tab is open and active - exactly what we needed.&lt;/p&gt;

&lt;p&gt;Getting the title of the currently active note was not so easy. My first guess was to use the mentioned instance of the &lt;code&gt;Editor&lt;/code&gt; available through the &lt;code&gt;EditorCommand&lt;/code&gt;. Unfortunately, I found no useful way to get the desired title. After searching the &lt;a href="https://docs.obsidian.md/Home" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; (that has room to improve) and trying different things out, I ended up with &lt;code&gt;plugin.app.workspace.getActiveFile()?.basename;&lt;/code&gt;. I'm not sure, weather its the best way or not. So please let me know if there is a better way!&lt;/p&gt;

&lt;p&gt;Well, we have a command, the notes title, but the Gemini response is missing. Luckily, Google provides an easy to use &lt;a href="https://www.npmjs.com/package/@google/generative-ai" rel="noopener noreferrer"&gt;NPM package&lt;/a&gt; and the first basic command implementation was done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generate-note&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generate Note with Gemini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;editorCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveFile&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Notice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not get filename&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastLine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Write a me an Obsidian Markdown Note without the Title on:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContentStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunkText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCursor&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastLine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Notice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;✅ Finished&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, I wanted to use the stream-based response for a more responsive note generation. To do this, we need to append the current chunk to the end of the editor using &lt;code&gt;replaceRange&lt;/code&gt;and &lt;code&gt;setCursor&lt;/code&gt;. Just using &lt;code&gt;replaceRange&lt;/code&gt; did not work, because it keeps the cursor at the original position.&lt;/p&gt;

&lt;p&gt;Alright, the editor command is up and running - only task left is to publish? It would be possible, but as I like to automate things, I wanted to add a CI pipeline before publishing the first version. The &lt;a href="https://github.com/BjarneRentz/obsidian-gemini-generator/blob/master/.github/workflows/release.yml" rel="noopener noreferrer"&gt;pipeline&lt;/a&gt; is triggered by a git tag and performs the two simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the plugin (&lt;code&gt;npm ci&lt;/code&gt;and &lt;code&gt;npm run build&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a draft release using the Github CLI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thus, for a new release, I can use the recommended command &lt;code&gt;npm version (patch|minor|major)&lt;/code&gt;, do a &lt;code&gt;git push --tags&lt;/code&gt;, verify the release and publish it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submitting the Plugin
&lt;/h2&gt;

&lt;p&gt;My original goal was to submit the plugin as soon as possible and provide new features as updates. However, during development I came up with new ideas: custom prompts, being able to change the default prompt, response processing, etc.&lt;br&gt;
However, I decided to postpone them until after the initial release in order to gain experience in submitting and managing the first public release.&lt;/p&gt;

&lt;p&gt;Hence, it was time to submit the first version of &lt;em&gt;Gemini Generator&lt;/em&gt;. As mentioned above, a Github release is already created through the pipeline. However, in order for the plugin to be found by Obsidian, you need to submit the plugin for review and approval, as described in this &lt;a href="https://docs.obsidian.md/Plugins/Releasing/Submit+your+plugin" rel="noopener noreferrer"&gt;guide&lt;/a&gt;. During this process, my pull request encountered a strange error where the pipeline performing some automated checks failed to checkout the pull request. I asked for help on the official Obsidian Discord server (which I can strongly recommend!), but the suggested fixes did not work. In the end, I had to close the PR and create a new one. After a few days, I got the approval and the first version of &lt;em&gt;Gemini Generator&lt;/em&gt; was publicly available!&lt;/p&gt;

&lt;p&gt;In part two we will improve the architecture of the plugin, add basic error handling and implement a first basic version of automatic response processing. So, if you're interested, don't forget to follow.&lt;/p&gt;

&lt;p&gt;If you want to have a look at source code as well, you can find the Github Repo &lt;a href="https://github.com/bjarneRentz/obsidian-gemini-generator" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>obsidian</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
