<?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: Dustin Runnells</title>
    <description>The latest articles on DEV Community by Dustin Runnells (@drunnells).</description>
    <link>https://dev.to/drunnells</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%2F939420%2F8a57389c-e8e8-4bf6-b370-bc1c0b653897.jpg</url>
      <title>DEV Community: Dustin Runnells</title>
      <link>https://dev.to/drunnells</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/drunnells"/>
    <language>en</language>
    <item>
      <title>Steam! ... almost.</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Sun, 22 Mar 2026 22:15:24 +0000</pubDate>
      <link>https://dev.to/drunnells/steam-almost-26pn</link>
      <guid>https://dev.to/drunnells/steam-almost-26pn</guid>
      <description>&lt;p&gt;Jumped through a lot of hoops to get this far! It's kind of funny, with vibe coding things moved so fast, but so far I think I've spent about 30% of this entire project just getting Steam configured. It isn't even the in-app stuff, just getting all the assets created in the various sizes and figuring out how to use their package uploader. It was educational, though! I'm sure it will go faster next time.. and that is kind of the point of the entire project anyway, just to learn. &lt;/p&gt;

&lt;p&gt;I've submitted the store presence for review. After that I think that I might be able to start inviting a few friends to play as I finalize this simple game. &lt;/p&gt;

&lt;p&gt;Here is the beta preview of the Steam store page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxaq16ta8i3667l1voro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxaq16ta8i3667l1voro.png" alt=" " width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AND I added a few achievements too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp4otyp159pqt3loohkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp4otyp159pqt3loohkd.png" alt=" " width="278" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think that I'll add a few more visuals, test out the fairness of the current game and then call this one done-for-now before moving on to the next thing! Getting to that done-for-now phase, might take a few more weeks, which was longer than expected.. but we'll see!&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>buildinpublic</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Shields Up!</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Thu, 19 Mar 2026 10:25:36 +0000</pubDate>
      <link>https://dev.to/drunnells/shields-up-2old</link>
      <guid>https://dev.to/drunnells/shields-up-2old</guid>
      <description>&lt;p&gt;Since the last update, the prototype has a better menu, "you died", game-over screens and improved feedback and power-ups. There is now a shield power-up to protect the rover from the UFO lasers for a few seconds. I'm also now starting the game without the turret, I'm hoping that adds some interesting stuff to the game play on levels where you don't have the turret until you collect it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F089lendujoei5fl5kukp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F089lendujoei5fl5kukp.gif" alt=" " width="540" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a bunch of new sounds too, maybe I'll do a proper youtube video so that it is audible soon. &lt;/p&gt;

&lt;p&gt;Oh, AND my Steamworks application was finished/accepted, so I'll start working on making this a real Steam game soon.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>buildinpublic</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Title Screen, Power-ups and Sound</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Tue, 17 Mar 2026 09:59:11 +0000</pubDate>
      <link>https://dev.to/drunnells/title-screen-power-ups-and-sound-36hd</link>
      <guid>https://dev.to/drunnells/title-screen-power-ups-and-sound-36hd</guid>
      <description>&lt;p&gt;Over the past week, I added a title screen and main menu. Sticking with my mostly-vibecoded theme, I just handed Chat GPT the 3 source images that I used for the 3d models and asked it to make a nice title screen background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zueb01ebttmpm06m8ge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zueb01ebttmpm06m8ge.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also wanted a fancier HUD, which took longer than I expected for Codex to get right to my liking. I based it on another mockup that I had ChatGPT make.&lt;/p&gt;

&lt;p&gt;Mockup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d73hvds2nphljod1cyj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d73hvds2nphljod1cyj.jpg" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implemented in game:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkorrihj2tuxaurmle2oj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkorrihj2tuxaurmle2oj.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as you can see, I've started adding some power-ups. For this I just generated some flat 2d icons and asked the agent to put a 3d shape rotating around it. We went through a few iterations, and this is what I settled on for now. &lt;/p&gt;

&lt;p&gt;I think that we'll make it so you have to manage your ammo, health and rocket fuel (jumping) through the power-ups. I'm also floating around maybe having a shield power-up for the rover and maybe an EMP to destroy all the UFOs in range. &lt;/p&gt;

&lt;p&gt;Not seen here, I've been working on adding some sounds through whatever free sounds I can find online and modifying them with &lt;a href="https://www.audacityteam.org/" rel="noopener noreferrer"&gt;Audacity&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've also taken the first step toward getting the game onto Steam eventually. I've registered with their developer program.. we'll see how that goes. With all the sound and assets, this might work better on desktop than mobile, which was my original platform of choice.&lt;/p&gt;

&lt;p&gt;Next - Maybe start planning the levels out!&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>buildinpublic</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Dustwalker - The beginning</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Sun, 08 Mar 2026 12:13:54 +0000</pubDate>
      <link>https://dev.to/drunnells/dustwalker-the-beginning-pd8</link>
      <guid>https://dev.to/drunnells/dustwalker-the-beginning-pd8</guid>
      <description>&lt;p&gt;It's been a while since I played with Godot. &lt;a href="https://dev.to/drunnells/series/20096"&gt;I have a series posted here from a few years ago&lt;/a&gt; (pre-chatgpt) when I made some good progress on a game. I never did finish that project, but it was a lot of fun to start to learn the technologies behind game development. &lt;a href="https://drunnells.itch.io/button-command" rel="noopener noreferrer"&gt;Later I made a game jam entry where I experimented with crude 2d assets that were AI generated.&lt;/a&gt; All fun stuff. &lt;/p&gt;

&lt;p&gt;Today though, with things like Codex, LLMs have completely changed how I approach coding projects. Since ChatGPT, I've made a fairly large, non-game, project (&lt;a href="https://reciscan.app" rel="noopener noreferrer"&gt;ReciScan&lt;/a&gt;) that evolved along with the AI tech. In the beginning 100% of the code was hand written. But slowly new features become easier to add by first consulting an LLM, then copy/pasting, and more recently I hardly do more than describe and test for certain features. I've even added functionality that was well beyond my ability a few years ago just by "vibe coding". I spent over a decade as a professional developer. I know enough to use the best tool for the job.. and in my humble opinion, the best tool for the job of most software development these days is probably not a human. Sure, creative guidance, technical direction and those things still require humans.. but I am really starting to feel the future sneak up on us fast in this space! I'm open to disagreement and debate, but I just can't believe that we'll be spending much more time making more programing languages for humans to write better programs.. the next ones will be for AIs.&lt;/p&gt;

&lt;p&gt;Anyway, a week ago today, I decided that I needed an excuse to start to migrate away from GitHub to a different platform (I have my reasons). I created a new &lt;a href="https://codeberg.org/" rel="noopener noreferrer"&gt;Codeberg&lt;/a&gt; repo and dreamed up a game project, with no intention of writing any code by hand. I settled on a reimagining of the old Atari game Moon Patrol. I am mind-blown about what can by prompting alone with the tools available right now. I have a full time job and 4 kids and other projects, but somehow in my spare time (less than 8 hours) in a single week, I have an almost playable game that I think looks pretty darn nice! &lt;/p&gt;

&lt;p&gt;I had Gemeni create a few images for the rover and settled on this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cs85e9ch5yiyyz5dobw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cs85e9ch5yiyyz5dobw.png" alt=" " width="754" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I used &lt;a href="https://www.meshy.ai/" rel="noopener noreferrer"&gt;Meshy.ai&lt;/a&gt; to turn it into a 3d object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqqefxat10slezu81x6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqqefxat10slezu81x6y.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It didn't come out perfect (the middle wheels are missing), but it was a quick fix in Blender.&lt;/p&gt;

&lt;p&gt;I did the same with a lunar base and a UFO. Several hours of prompting and tuning later, I  had this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bllvox5ghjt0q6bom4s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bllvox5ghjt0q6bom4s.gif" alt=" " width="540" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NOT BAD! With a little more effort, I think that I might even release it. Maybe on Android/iOS where I'm comfortable, or maybe I'll check out what the authoring process is like on Steam!&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>buildinpublic</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Creating an MCP Server for the Pulsar Editor</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Fri, 20 Jun 2025 16:02:28 +0000</pubDate>
      <link>https://dev.to/drunnells/creating-an-mcp-server-for-the-pulsar-editor-1m5</link>
      <guid>https://dev.to/drunnells/creating-an-mcp-server-for-the-pulsar-editor-1m5</guid>
      <description>&lt;p&gt;Over the last few months there has been a lot of talk about &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; for adding tooling to &lt;a href="https://en.wikipedia.org/wiki/Large_language_model" rel="noopener noreferrer"&gt;LLMs (Large Language Models)&lt;/a&gt;. Prior to MCP, I have written applications to interact with LLMs beyond chat simply by asking the LLM to respond in a specific format and parsing the results. This worked great! BUT, having a standard way to call tools without needing to write your own parsing code every time with MCP feels like the right way to go. &lt;/p&gt;

&lt;p&gt;As I continue my projects, I have observed that there are lots of new applications integrating LLMs in every way imaginable. One of the most interesting LLM integrations to me is the concept of having an LLM assist in code writing - right in your editor! My editor of choice for my coding projects is &lt;a href="https://github.com/pulsar-edit" rel="noopener noreferrer"&gt;Pulsar&lt;/a&gt; (formally &lt;a href="https://atom-editor.cc/" rel="noopener noreferrer"&gt;Atom&lt;/a&gt;). Since the early days of &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; until now, I've mostly just copy/pasted whatever I needed an AI to help me with into my editor. But the "&lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;vibe coding&lt;/a&gt;" trend and the time lost to copy/paste has made me a little jealous of folks that use editors that have a direct integration with AI/LLM coding assistants. This coupled with a &lt;a href="https://github.com/tidev/pulsar-titanium/pull/685" rel="noopener noreferrer"&gt;recent experience modifying a Pulsar package&lt;/a&gt; that I use frequently and &lt;a href="https://dev.to/drunnells/controlling-chrome-with-an-anythingllm-mcp-agent-5891"&gt;experimenting with using MCP as a client in AnythingLLM&lt;/a&gt; has given me the confidence to start a new Pulsar package to run an MCP server inside Pulsar to give an LLM control over common editing functions. Ultimately, I'd like to chat with the LLM about the code I'm working on and let it make adjustments/additions.&lt;/p&gt;

&lt;p&gt;This week I took the first step with &lt;a href="https://github.com/drunnells/pulsar-edit-mcp-server" rel="noopener noreferrer"&gt;my own Pulsar package&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;At the time of this writing, this is little more than a proof of concept as a combination of the starter Pulsar &lt;a href="https://web.pulsar-edit.dev/packages/package-generator" rel="noopener noreferrer"&gt;package-generator&lt;/a&gt; package, the &lt;a href="https://atom-flight-manual-archive.github.io/hacking-atom/sections/package-word-count/" rel="noopener noreferrer"&gt;Atom package creation tutorial&lt;/a&gt;  and the example from the official &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP Typescript SDK&lt;/a&gt; with some editor tool functions registered. Hopefully it will become more soon, since I am looking forward to actually using it to build stuff!&lt;/p&gt;

&lt;p&gt;I did run into some hurdles along the way, which I'll mention below. Here are the steps that I've taken so far to get here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Created a new Pulsar package with &lt;a href="https://web.pulsar-edit.dev/packages/package-generator" rel="noopener noreferrer"&gt;https://web.pulsar-edit.dev/packages/package-generator&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removed the unneeded default UI elements from the view&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added a status tile to indicate when the MCP server is listening&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pasted in the Streamable HTTP MCP server code from the official MCP typescript quickstart page: &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol/typescript-sdk&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added the dependencies for the MCP SDK and strict typing validation to my package.json:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@modelcontextprotocol/sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.13.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"zod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.25.67"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hit some roadblocks! Pulsar seems to be bundled with an older version of Node JS, that does not like the &lt;a href="https://nodejs.org/api/esm.html#node-imports" rel="noopener noreferrer"&gt;"node:" protocol&lt;/a&gt; in import and require! This seems to be everywhere in the MCP Typescript SDK. I grepped for the "node:" instances and made my edits to the dependencies and their dependencies to remove the text "node:" from the lines and require/import the old way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More roadblocks! The older Node JS also doesn't like &lt;code&gt;Object.hasOwn()&lt;/code&gt; in one of the dependencies (marge-descriptors). Replaced that with &lt;code&gt;Object.prototype.hasOwnProperty.call()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Still more roadblocks! Something seems incompatible with the old Node JS and &lt;code&gt;crypto.randomUUID()&lt;/code&gt;. I'm not interested figuring out why, so I found a &lt;a href="https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid" rel="noopener noreferrer"&gt;simple javascript UUID function on Stack Overflow&lt;/a&gt; and call that one instead.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Woohoo! My Pulsar package now runs without errors! To make the ppm (Pulsar Package Manager) capable of doing these edits for me instead of asking users to manually find and do the edits in the dependencies, I ran &lt;a href="https://github.com/ds300/patch-package" rel="noopener noreferrer"&gt;patch-package&lt;/a&gt; on each of the dependencies that I modified:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx patch-package @modelcontextprotocol/sdk
npx patch-package express
npx patch-package router
npx patch-package body-parser
npx patch-package merge-descriptors
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And updated my Pulsar package's package.json to do the patching:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"patch-package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"patch-package"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With &lt;em&gt;server&lt;/em&gt; being my instance of &lt;code&gt;McpServer()&lt;/code&gt; I can now register some tools from the Pulsar API to it. Here is an example for moving the cursor around:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move-cursor&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Move Cursor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Move cursor to location in editor.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&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;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;atom&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;getActiveTextEditor&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;setCursorBufferPosition&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&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="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Moved cursor to row &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, column &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;column&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;/li&gt;
&lt;li&gt;&lt;p&gt;Now I'll launch another instance of Pulsar and start the MCP Listener with the menu item that I created (see source). I also observed that the "MCP:On" status tile I added is shown in a status tile in the lower left of Pulsar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To see if the MCP server is actually working inside my Pulsar package, we can use the official MCP Inspector from the command line:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The inspector connects to an MCP server and runs a little http server that you can point a browser to to allow you to send MCP commands and see the responses. This lets you confirm things are working as you expect before pointing your LLM to your MCP Server. Executing the command above gae me a URL to visit in my browser:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=&amp;lt;Random_Token&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next I executed some of the new tools in the MCP inspector with some parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yun8fwxhxgao7d4n7zl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yun8fwxhxgao7d4n7zl.png" alt=" " width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All went smoothly and I can see my Pulsar editor react to the MCP tool!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ok, now to finally point &lt;a href="https://github.com/Mintplex-Labs/anything-llm" rel="noopener noreferrer"&gt;AnythingLLM&lt;/a&gt; (my current favorite desktop LLM client) to the MCP server and tell the LLM to do something in the Pulsar editor! AnythingLLM keeps it's mcpServers config in this file on Mac:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/Library/Application\ Support/anythingllm-desktop/storage/plugins/anythingllm_mcp_servers.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The configuration for this MCP server should look like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"pulsar-edit-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"alwaysAllow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"streamable"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now I'll try it out and ask the LLM to add some text to the editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsfdzh36jr76050vb4zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsfdzh36jr76050vb4zo.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's working! &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next I'll add some more editor functions and recruit some contributors (&lt;a href="https://github.com/drunnells/pulsar-edit-mcp-server" rel="noopener noreferrer"&gt;perhaps YOU!?&lt;/a&gt;). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thanks for reading!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>mcp</category>
      <category>llm</category>
      <category>pulsaredit</category>
      <category>pulsar</category>
    </item>
    <item>
      <title>Controlling Chrome with an AnythingLLM MCP Agent</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Mon, 26 May 2025 22:22:49 +0000</pubDate>
      <link>https://dev.to/drunnells/controlling-chrome-with-an-anythingllm-mcp-agent-5891</link>
      <guid>https://dev.to/drunnells/controlling-chrome-with-an-anythingllm-mcp-agent-5891</guid>
      <description>&lt;p&gt;This turned out to be a lot easier than I expected it to be! This is my first experiment with &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;, the Model Context Protocol. My previous experience with agents and tools required telling the LLM how to do things, either in the main context of the conversation in plain english or relying on whatever LLM application I'm using to have some interface to make it easier. MCP provides a standard way to link the LLM application you are using with the instructions to the agent on what tools and abilities are available to it and how to use them.&lt;/p&gt;

&lt;p&gt;For today's experiment, I wanted to control my web browser (Chrome) by simply telling the LLM what I want to do in a chat. I'm chatting with the LLM using AnythingLLM on my Macbook Pro and have a Chrome window open... wouldn't it be nice to say "Hey, go start a blog post for me"!?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anythingllm.com/" rel="noopener noreferrer"&gt;AnythingLLM&lt;/a&gt; is becoming my tool of choice for connecting to my local &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; server and they recently added MCP support.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.google.com/chrome/" rel="noopener noreferrer"&gt;Google Chrome&lt;/a&gt; browser has a debugging mode that runs a server for remote tools to connect to it.&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://github.com/lxe" rel="noopener noreferrer"&gt;Aleksey Smolenchuk&lt;/a&gt; has written a &lt;a href="https://github.com/lxe/chrome-mcp" rel="noopener noreferrer"&gt;Chrome MCP Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's see if we can connect all three and do something cool! &lt;/p&gt;

&lt;p&gt;First, let's start Chrome in debugging mode. On my Mac, this is done with the --remote-debugging-port command line option, but also requires the --user-data-dir to be something other than the default, so we'll use both options. Be sure to close out all Chrome windows, then from the command line do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/Applications/Google&lt;span class="se"&gt;\ &lt;/span&gt;Chrome.app/Contents/MacOS/Google&lt;span class="se"&gt;\ &lt;/span&gt;Chrome &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222 &lt;span class="nt"&gt;--user-data-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should start and tell you that it is running on 127.0.0.1 and your specified port:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12sea5msdq6he3r4iiln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12sea5msdq6he3r4iiln.png" alt="Command Line" width="800" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next I downloaded the Chrome MCP Server and followed the instructions on Github, which boiled down to just:&lt;/p&gt;

&lt;p&gt;Clone the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/lxe/chrome-mcp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;chrome-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Bun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; bun

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

&lt;/div&gt;



&lt;p&gt;Install the dependencies and start the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you should see the MCP server connect to the Chrome remote debugger on port 9222 and start listening for MCP traffic on port 3000:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0ayjfsfn3043a3s3iwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0ayjfsfn3043a3s3iwp.png" alt="Command Line" width="363" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All that's left now is to get AnythingLLM to start using the Chrome MCP server. There are instructions on the Github page for this server, but they will be slightly different for AnythingLLM. &lt;/p&gt;

&lt;p&gt;If you are on a Mac, the AnythingLLM MCP servers setup is in your home directory in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Library/Application Support/anythingllm-desktop/storage/plugins/anythingllm_mcp_servers.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It probably already exists, but has an empty mcpServers section. We'll copy the example from the Chrome MCP server instructions and add a "type" so that it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chrome-control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/sse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"alwaysAllow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sse"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there we should be good to go! Let's fire up AnythingLLM. We should see the MCP server in Settings-&amp;gt;Agent Skills:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xrta4iijlky3o1ukqfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xrta4iijlky3o1ukqfq.png" alt="AnythingLLM Settings" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if we start a chat and ask nicely with "&lt;strong&gt;&lt;code&gt;@agent&lt;/code&gt;&lt;/strong&gt;" in our sentence, we can watch the the LLM do it's magic! Watch me ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;@agent&lt;/code&gt; I want to create a new blog post on dev.to. Can you take me there in a new chrome tab?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2q9xd28l9bbui7y5itn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2q9xd28l9bbui7y5itn.gif" width="600" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was fun! Maybe I'll try to write my own MCP server next!&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>anythingllm</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Writing an AnythingLLM Custom Agent Skill to Trigger Make.com Webhooks</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Thu, 28 Nov 2024 03:08:55 +0000</pubDate>
      <link>https://dev.to/drunnells/writing-an-anythingllm-custom-agent-skill-to-trigger-makecom-webhooks-1dn0</link>
      <guid>https://dev.to/drunnells/writing-an-anythingllm-custom-agent-skill-to-trigger-makecom-webhooks-1dn0</guid>
      <description>&lt;p&gt;Recently I've been experimenting with running a local &lt;a href="https://github.com/ggerganov/llama.cpp" rel="noopener noreferrer"&gt;Llama.cpp&lt;/a&gt; Server and looking for 3rd party applications to connect to it. It seems like there are a lot of popular solutions to running models downloaded from &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Huggingface&lt;/a&gt; locally, but many of them want to import the model themselves using the Llama.cpp or &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; libraries instead of connecting to an external provider. I'm more interested in using this technology as a server so that I start it once and allow clients to connect to it without needing to shutdown and go through the long, resource intensive, start-up process of each application. One of the applications that I've found great for chatting with in my setup is &lt;a href="https://anythingllm.com/" rel="noopener noreferrer"&gt;AnythingLLM&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;AnythingLLM can connect to multiple providers, including Llama.cpp's llama-server (As an OpenAI compatible API), Ollama, &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://www.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;, &lt;a href="https://gemini.google.com/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt;, and others. Another cool feature of AnythingLLM is that it lets you use the LLM as an &lt;em&gt;agent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents&lt;/strong&gt; are LLMs that have access to tools, so that they can do more than just chat with the user. You can ask an Agent to go execute some command, perhaps a web search or image generator with some parameters. This is powerful. With an agent, you can have a conversation with the LLM about something, and then say "Go do something with this information". Imagine telling your LLM to get today's headlines from your favorite news source, review for anything related to some specific subject that interests you and then email the headlines to you!&lt;/p&gt;

&lt;p&gt;Out of the box, AnythingLLM has a few agents built in, including a chart generator, a web search, a web scraper, and SQL connector to query databases. Those are awesome. BUT, more recent versions of AnythingLLM let you create your own agents with JavaScript/Node.js!&lt;/p&gt;

&lt;p&gt;If you know how to interface with APIs, this can be a game changer.. you can tell your LLM to do anything that you can access via a REST endpoint. But you know what would be even better? Not needing to do the API work yourself. There are automation services that do all the API work for you and let you automate and chain tasks from one API to another, such as &lt;a href="https://www.make.com/" rel="noopener noreferrer"&gt;Make.com&lt;/a&gt;, &lt;a href="https://ifttt.com/" rel="noopener noreferrer"&gt;IFTTT&lt;/a&gt; and &lt;a href="https://zapier.com/" rel="noopener noreferrer"&gt;Zapier&lt;/a&gt;. These automation services offer a webhook trigger that lets you easily pass some parameters to a unique URL to kick off automations using pre-built connections to popular services and utilities to enable such actions as posting to &lt;a href="https://x.com/" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://www.instagram.com/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; or update a &lt;a href="https://workspace.google.com/products/sheets/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; spreadsheet, etc. &lt;/p&gt;

&lt;h2&gt;
  
  
  Make.com
&lt;/h2&gt;

&lt;p&gt;For my example I wanted to have my LLM agent update a Google Sheets spreadsheet with contact information. Make.com has both a connector to do Google Sheets connections and has a webhook trigger. Make.com has lots of other connectors too, so you will want to explore Make.com's library of connections to do some really interesting stuff.&lt;/p&gt;

&lt;p&gt;Before we set up our agent, let's get the Make.com side of things set up.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First we'll need a spreadsheet to insert our date into. Go to Google Sheets and create a new spreadsheet to store the Name, Email and Phone Number of contacts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ohm7ba6gtmjr8n0dt16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ohm7ba6gtmjr8n0dt16.png" alt=" " width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next we'll create a new Scenario on Make.com. Our first connector will be the Webhook. A webhook is just a URL intended to receive data (instead of showing a web page). You can just go with the default settings and give it a name. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37b5yggbjx8iqwr978b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37b5yggbjx8iqwr978b4.png" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make.com will generate a unique URL for you to use. Copy the URL down. We'll need it to continue the configuration and for the agent setup later.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8f89vvk1wdff4g8wuhim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8f89vvk1wdff4g8wuhim.png" alt=" " width="446" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The webhook will need to receive an actual call to know what data to expect. Using curl, let's send it a JSON payload of the 3 fields from our spreadsheet and some test data.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://hook.us2.make.com/testqrd3j8469g856t16hp9l95yeowv9 &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Jon Smith","email":"jsmith@example.com","phone":"555-555-5555"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The Make.com interface should reflect that it received the call immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next add a connector for Google Sheets by clicking the big plus sign to the right of our new Webhook connector. You'll be asked to connect your Google account, select the document from your Google Drive and select the sheet name (probably Sheet1).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the Google Sheets connector added and linked to your spreadsheet, you'll see a section for Values. These values can be received from earlier connections in your Make.com scenario - in our case, the Webhook. For each of Name, Email and Phone, select the Webhook values shown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88mzgjufe4czvvuspvfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88mzgjufe4czvvuspvfc.png" alt=" " width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click OK to complete the Google Sheets connection setup.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Our Make.com scenario is now complete. To save it click "Run Once" on the bottom left.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once the scenario is saved, turn on "Immediately as Data Arrives" on the bottom. This will let let your webhook update your spreadsheet even when you are not signed into Make.com.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the curl command from above again with a some different data. You should see your spreadsheet update almost immediately and we'll know that your webhook is working and ready for our next steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Before we continue, do keep in mind that giving an LLM the ability to do things in the real world is both powerful and dangerous. Think carefully about potential consequences before giving it access to important documents and tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With that, let's get started with the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  AnythingLLM
&lt;/h2&gt;

&lt;p&gt;If you don't already have it, download the AnythingLLM desktop application for your platform &lt;a href="https://anythingllm.com/desktop" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In my examples I'll be using my Macbook Pro to run AnythingLLM and connecting to llama-server running on an &lt;a href="https://archlinux.org/" rel="noopener noreferrer"&gt;Arch Linux&lt;/a&gt; server with the &lt;a href="https://huggingface.co/mradermacher/Meta-Llama-3.1-70B-Instruct-i1-GGUF" rel="noopener noreferrer"&gt;Meta Llama 3.1 Instruct model (70B)&lt;/a&gt;. However, you can do all of this just by linking AnythingLLM to the &lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;OpenAI API with an API Key&lt;/a&gt;. I will not go into the details of the initial setup of AnythingLLM in this post, but once you get it up and running, have a workspace created and can chat in the workspace with your LLM, you are ready to continue to the next step. Consult the &lt;a href="https://docs.anythingllm.com/" rel="noopener noreferrer"&gt;AnythingLLM Documentation&lt;/a&gt; for assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Configuration
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Once you have a Workspace running, go to the workspace settings (Gear icon to the right of the workspace name on the left side of your screen). Click the Agent Configuration tab at the top and select a Workspace Agent LLM Provider. This can be the same LLM provider you use for chat.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next click Configure Agent Skills. This screen should have all of the built-in agent skills listed, including RAG and website scraping. You can optionally turn on Generate charts, Web Search and a couple others that you might find useful. At the bottom might be "Custom Skills", which will be empty until we create one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try out one of the built-in agents to get an idea of how they work. To call an agent function, prefix your request with &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/agent"&gt;@agent&lt;/a&gt;&lt;/strong&gt;. For example, ask your LLM to check the top tech headlines on CNN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2sz097nw7gmvbge5s1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2sz097nw7gmvbge5s1x.png" alt=" " width="773" height="417"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Writing a Custom Agent Skill
&lt;/h2&gt;

&lt;p&gt;Now that you know how to use an agent and we have Make.com ready to go to communicate with our future agent, let's get get the agent written.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Find your &lt;code&gt;agent-skills&lt;/code&gt; directory. On Mac, this is in your home directory under: Library/Application Support/anythingllm-desktop/storage/plugins/agent-skills , but &lt;a href="https://docs.anythingllm.com/agent/custom/developer-guide#where-to-place-your-custom-agent-skill-code" rel="noopener noreferrer"&gt;check the documentation for your platform&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In agent-skills, make a new directory for your agent:&lt;br&gt;
&lt;code&gt;mkdir sheet-update-agent&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Define your agent in plugin.json. &lt;/p&gt;

&lt;p&gt;Create a new file called &lt;strong&gt;plugin.json&lt;/strong&gt; inside the sheet-update-agent directory and paste in the below. I'll discuss what all this means after.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hubId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sheet-update-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Update Google Sheet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skill-1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Update Google Sheet via Make.com Webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@drunnells"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/drunnells"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"setup_args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"placeholder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://hook.us2.make.com/your-custom-url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The URL to send the post request to"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entrypoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handler.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Contact name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Contact email address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Contact phone number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"examples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add Bob Smith's contact information to the contact spreadsheet - Bob Smith, bsmith@example.com, 123-456-7891"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Bob Smith&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;bsmith@example.com&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;phone&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123-456-7891&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Here is Jane Doe's info: jdoe@sample.com, 555-555-5555"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Jane Doe&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;jdoe@sample.com&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;phone&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;555-555-5555&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add Billy Exampleman to my contacts, his phone number is (444)333-2211 and email billy_e@test.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Billy Exampleman&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;billy_e@test.net&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;phone&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;444-333-2211&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;We'll break above down into 4 sections so you know what each is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main plugin definition / other_properties&lt;/strong&gt; - This is where the plugin name, details and author info is.&lt;/p&gt;

&lt;p&gt;You'll need to make sure to update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;hubId&lt;/em&gt; - This should match the directory name inside agent-skills&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;name&lt;/em&gt; - The name of your custom skill / agent&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;description&lt;/em&gt; - A short description about your skill&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;author&lt;/em&gt; - Your author id&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;author_url&lt;/em&gt; - A link to more information about you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup Args&lt;/strong&gt; - This is how you define the configuration screen for your agent. In our case we let the user paste in their Make.com webhook URL. The values here will be exposed to your Javascript as the runtimeArgs[] array. You will observe that the empty "value" inside setup_args will become populated with the saved data once it is set by the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entrypoint&lt;/strong&gt; - Every parameter that you want to send to the JavaScript function for your agent is set here. Notice that handler.js is the filename. For our example, we'll be passing along the name, email and phone of each contact from the LLM conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt; - The &lt;a href="https://docs.anythingllm.com/agent/custom/plugin-json" rel="noopener noreferrer"&gt;AnythingLLM documentation&lt;/a&gt; says that you should provide 1-3 examples of how the user might indicate the parameters that will be sent to your JavaScript function.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write your agent's javascript to call the webhook in &lt;strong&gt;handler.js&lt;/strong&gt; and put it in the same directory with your plugin.json. The below JavaScript is just a function that takes the passed in name/email/phone parameters and does a post to our webhook URL (this.runtimeArgs["postUrl"]) set by the user in the AnythingLLM Custom Skill configuration screen.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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;introspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Received Parameters: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phone&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Runtime Arguments: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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;runtimeArgs&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;postUrl&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;runtimeArgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postUrl&lt;/span&gt;&lt;span class="dl"&gt;"&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;postUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error: postUrl is not configured.&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;introspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Attempting to post to webhook: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postUrl&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="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;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error: Missing required parameters. Please provide name, email, and phone.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;
        &lt;span class="p"&gt;})&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Document updated successfully!&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Failed to update document. Status Code: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Failed to update document. Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="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;/li&gt;
&lt;li&gt;
&lt;p&gt;Setup your new custom agent in the AnythingLLM UI.&lt;/p&gt;

&lt;p&gt;a. Go to the gear icon to the right of your workspace. Click Agent Configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4zxj6dpq4hh205c8bt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4zxj6dpq4hh205c8bt8.png" alt=" " width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;b. Select the Workspace Agent LLM Provider - Your Agent will not work until you do this! I just select the same LLM that I chat with in my workspace, but note that not all LLM models are equally good at being agents.&lt;/p&gt;

&lt;p&gt;c. Click &lt;strong&gt;Configure Agent Skills&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;d. You should see your new skill under Custom Skills at the bottom.&lt;/p&gt;

&lt;p&gt;e. Click our new "Update google sheet" skill. It should have the postUrl field that we defined in our plugin.json. This is where we'll paste our Make.com webhook URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayerbdbg3qomubpf9lmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayerbdbg3qomubpf9lmb.png" alt=" " width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now it's time to test! Return to a chat in your workspace and type something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@agent update my contacts with Thomas Jupiter - tjup@fakeemail.com - 777-888-9999&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If all went well, you'll see the new row appear automatically in your Google Sheets spreadsheet and this in the AnythingLLM chat:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbr4trqxzkgshwp2pd7f1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbr4trqxzkgshwp2pd7f1.png" alt=" " width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now for something extra cool, get your agent to fetch some data from one skill and have it then update your spreadsheets with the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kvz9yukim8j821wb80w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kvz9yukim8j821wb80w.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this blog post, we explored how to write a custom agent skill in AnythingLLM that integrates seamlessly with Make.com webhooks. You learned how to configure a Make.com scenario to handle webhook data and update a Google Sheets spreadsheet, then connect that setup to an LLM agent. This approach unlocks powerful automation possibilities by enabling LLM agents to interact with Make.com's extensive library of pre-built connectors. By leveraging these tools, you can create sophisticated workflows that combine the conversational capabilities of LLMs with the automation power of Make.com, opening the door to countless use cases without having to write complex API integrations yourself. &lt;/p&gt;

&lt;p&gt;I'd love to hear what agents you are creating with this method, please post them in the comments!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Web3 UI For Simple Smart Contract</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Sat, 16 Nov 2024 14:18:33 +0000</pubDate>
      <link>https://dev.to/drunnells/web3-ui-for-simple-smart-contract-4n75</link>
      <guid>https://dev.to/drunnells/web3-ui-for-simple-smart-contract-4n75</guid>
      <description>&lt;p&gt;Let's build a web frontend to a smart contract! This is a followup to my &lt;a href="https://dev.to/drunnells/simple-start-to-smart-contract-development-with-hardhat-4c1h"&gt;previous post&lt;/a&gt; about creating a simple smart contract with &lt;a href="https://soliditylang.org/" rel="noopener noreferrer"&gt;Solidity&lt;/a&gt; and &lt;a href="https://hardhat.org/" rel="noopener noreferrer"&gt;Hardhat&lt;/a&gt;. The instructions here assume you are picking up up with the same contract that we just deployed to our Hardhat environment.&lt;/p&gt;

&lt;p&gt;In the last post we created and tested a contract that will increment a counter stored in a state variable. Using the Hardhat console we called the incrementCount() and getCount() functions. In the real world, interfacing with a contract won't be through a development console. One way to create an application that calls these functions will be via Javascript (via the &lt;a href="https://ethers.org/" rel="noopener noreferrer"&gt;ethers.js&lt;/a&gt; library) in a web page - a &lt;strong&gt;Web3&lt;/strong&gt; application!&lt;/p&gt;

&lt;p&gt;As stated in the previous post, interacting with web3 applications requires a browser that has a built in wallet. In this simple example we'll use &lt;a href="https://metamask.io/" rel="noopener noreferrer"&gt;Metamask&lt;/a&gt;. Metmask comes pre-configured for Etherium and maybe a few other EVM based blockchains, but not our simulated blockchain in the Hardhat environment. To get this all running, we are going to first setup Metmask, then create the HTML/Javascript needed to call our contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metamask / Web3 Browser
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Metamask. I'll use the Chrome extension found &lt;a href="https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en" rel="noopener noreferrer"&gt;here&lt;/a&gt;. If you are a Chrome user, this will let you now view and interact with web3 content.&lt;/p&gt;

&lt;p&gt;I won't walk you through the initial setup, but you will probably be prompted to import an existing private key or generate a new one and write down the recovery phrase. Do that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next we'll add the Hardhat network to Metamask. Metamask supports any EVM you'd like, but it needs to be configured to do so. Usually this is just a matter of adding the chain ID and RPC URL. From inside Metamask (you might need to start it by clicking on your Chrome plugins and selecting it) you should see your public address in the top middle. To the left of your address there will be a dropdown that shows the current network. Click that to see what other networks are available:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki0l1gjcttjo3qq6bzcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki0l1gjcttjo3qq6bzcv.png" alt=" " width="393" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "&lt;strong&gt;Add Custom Network&lt;/strong&gt;". Fill in the Network Name with something like "&lt;strong&gt;Hardhat&lt;/strong&gt;", the Network RPC URL with the IP address and Port of your Hardhat Node, probably something like this if you are running it locally:&lt;br&gt;
&lt;code&gt;http://127.0.0.1:8545/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Enter the Chain ID of &lt;strong&gt;1337&lt;/strong&gt; and the symbol can just be &lt;strong&gt;ETH&lt;/strong&gt; for now. Note that we are &lt;u&gt;not&lt;/u&gt; dealing with real ETH on the real Ethereum network, but be very careful to stay on our Hardhat network if you have real ETH in your wallet.&lt;/p&gt;

&lt;p&gt;Now switch to the Hardhat Network that we just added in the Metamask plugin. In your terminal that is monitoring your running Hardhat node, you should see some activity as your wallet connects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since your Metamask wallet doesn't currently have any (fake) ETH, let's send it some. Get your public address from Metamask (at the top of the Metamask window, under the wallet's name, click the copy button). From your terminal window that is running the Hardhat &lt;em&gt;console&lt;/em&gt;, do:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;feeCollector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;]&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;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigners&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PasteYourMetamaskAddressHere&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.1&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;/code&gt;&lt;/pre&gt;


&lt;p&gt;If you go back to Metamask, you should see that you now have some ETH in your Hardhat wallet! Now we are ready to do some web3 transactions on our Hardhat network.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a Web3 Webpage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Let's create a simple webpage to view and increment our counter. I'm not going to use any heavy frameworks, just plain old HTML, Javascript and the ethers.js library. However, you won't be able to just point the browser to a .htm document, you'll need to be running a webserver somewhere for the Metamask plugin to work. Depending on your OS, you might be able to use a lightweight server like http-server or something locally.&lt;/p&gt;

&lt;p&gt;We'll need a few things from when we deployed our contract in the previous post. Refer back to the last post and grab the &lt;em&gt;contract address&lt;/em&gt; and contract's &lt;em&gt;ABI&lt;/em&gt; JSON array from the artifacts directory. We don't need the rest of the JSON from that file, just what is in the "abi" property, it should start with a &lt;strong&gt;[&lt;/strong&gt; and end with a &lt;strong&gt;]&lt;/strong&gt; and look something like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nonpayable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"constructor"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"internalType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uint256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uint256"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incrementCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nonpayable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Let's put this into some HTML and Javascript:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdnjs.cloudflare.com/ajax/libs/ethers/5.2.0/ethers.umd.min.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;The counter is at: &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"counterValue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"incrementBtn"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;"incrementButtonClicked();"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractAbi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inputs&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stateMutability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nonpayable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constructor&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inputs&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outputs&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;internalType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uint256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uint256&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stateMutability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inputs&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incrementCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outputs&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stateMutability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nonpayable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="c1"&gt;// Replace with your contract's ABI&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;contractAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x5fbdb2315678afecb367f032d93f642f64180aa3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Replace with your contract's deployed address&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;connectMetaMask&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contractAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;contractWithSigner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signer&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="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;estimateGas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;refreshCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connectMetaMask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Web3Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethereum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_requestAccounts&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCountFromContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&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;Calling getCount...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&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;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;incrementCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&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;Calling incrementCount...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&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;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incrementCount&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;incrementCount RESULT: &lt;/span&gt;&lt;span class="dl"&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="nx"&gt;result&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;Waiting for transaction to be mined...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the transaction to be mined&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;Mined.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;estimateGas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&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;Estimating Gas...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;estimatedGas&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;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;estimateGas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incrementCount&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;ESTIMATED GAS RESULT:&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;estimatedGas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toNumber&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refreshCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getCountFromContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curCount&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;getCount RESULT: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;curCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counterValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;curCount&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;incrementButtonClicked&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="nf"&gt;incrementCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractWithSigner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;refreshCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We should now be able to view our HTML document in a web browser with the Metamask plugin installed. I won't go through the Javascript, but if you are familiar with JavaScript and following the concepts and what we did in the Hardhat terminal previously, what is happening in the code should be fairly straight-forward. Metamask should prompt you that you are connecting to the site and you'll need to select the Hardnet network that we set up earlier. You should see something like this in the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawu289b43rq0r37710tz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawu289b43rq0r37710tz.png" alt=" " width="305" height="105"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If all went well, you can click on the "Increment" button. Metamask will let you know that you are about to make a transaction and inform you of the gas fee. You can Confirm this transaction in Metamask and see the count increment on both the website and in the terminal where you have the hardhat node running!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Congratulations, we are interacting with our contract through a web UI!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few notes as you dive deeper into Hardhat and Metamask for development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each transaction has an nonce. When you reset your hardhat node, that nonce gets reset and you might loose sync with what Metamask thinks is a unique nonce. When that happens, Metmask has an option to set a custom nonce with the transaction, or you can reset Metamask's nonces in Settings-&amp;gt;Advanced-&amp;gt;Clear Activity Tab data. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You'll need to redeploy your smart contract every time you restart your Hardhat node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you are writing contracts that will keep track of users by their public address and want to experiment in the Hardhat console with transactions form different users, you can impersonate different addresses in the console that were displayed when you first started the Hardhat node with something like this before you connect to the contract:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signers&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;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigners&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;newSigner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Change the 1 to a different number that corolates with one of the pre-generated testing addresses&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newMain&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;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newSigner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;newMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContractAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xYourContractAddress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>web3</category>
      <category>polygon</category>
    </item>
    <item>
      <title>Simple Smart Contract and Hardhat</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Sat, 16 Nov 2024 10:13:32 +0000</pubDate>
      <link>https://dev.to/drunnells/simple-start-to-smart-contract-development-with-hardhat-4c1h</link>
      <guid>https://dev.to/drunnells/simple-start-to-smart-contract-development-with-hardhat-4c1h</guid>
      <description>&lt;p&gt;Welcome to my series on Simple Web3 Smart Contract development! This post is mostly just my notes on how to get a super simple &lt;strong&gt;smart contract&lt;/strong&gt; (self executing program on a blockchain) written, deployed and called. We'll explore more of the Web3 side &lt;a href="https://dev.to/drunnells/web3-ui-for-simple-smart-contract-4n75"&gt;next post&lt;/a&gt;. I've tried to minimize the need for any large frameworks or other complications as much as possible, but once you get a grasp of what is here, it should be easy to integrate whatever frameworks you like for more complex interfaces.&lt;/p&gt;

&lt;p&gt;For me it is easiest to learn something new by doing. When dealing with blockchains and smart contracts however, that "doing" becomes expensive when you are trying to do something many times for educational purposes. Since every write to a blockchain costs something (on Ethereum Virtual Machine, or &lt;strong&gt;EVM&lt;/strong&gt;, based networks, this cost is referred to as a &lt;strong&gt;Gas Fee&lt;/strong&gt;). To avoid the cost of writing to a real blockchain, we'll use a tool called &lt;a href="https://hardhat.org/" rel="noopener noreferrer"&gt;Hardhat&lt;/a&gt;. Hardhat will simulate an EVM network/blockchain for us to play with. When you are ready to actually publish your Smart Contract to a real blockchain, you'll need to either run a node yourself or use a Node Provider like &lt;a href="https://www.alchemy.com/" rel="noopener noreferrer"&gt;Alchemy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post we'll create simple contract with the &lt;a href="https://soliditylang.org/" rel="noopener noreferrer"&gt;Solidity&lt;/a&gt; programming language that just increments a state variable and lets us get it's value. For me, the most compelling part of blockchain technology is the persistence of information that it offers. In smart contracts there are &lt;strong&gt;Local variables&lt;/strong&gt; that are only used to store temporary data while the contract is doing something, and &lt;strong&gt;State variables&lt;/strong&gt; that are persistent and store information forever. In our example today we'll create a contract that has a function that increments a counter state variable and also has a function to retrieve it's current value.&lt;/p&gt;

&lt;p&gt;Before we start, I want to introduce you to some terminology, you can skip this section if you are not new to the space. One key concept with blockchains is &lt;strong&gt;public/private keys and addresses&lt;/strong&gt;. If you are familiar with other encryption schemes, such as PGP/GPG, this is the same idea. Every user on an EVM network has a Public Key/Address and a Private key. The &lt;strong&gt;private key&lt;/strong&gt; is never shared with anyone, but your &lt;strong&gt;public address&lt;/strong&gt; is visible to anyone every time you write to the blockchain. Only the owner of a private key can prove that a public address belongs to them through a process known as &lt;strong&gt;signing&lt;/strong&gt;. An application (called a &lt;strong&gt;Wallet&lt;/strong&gt;) on your computer or phone will store your private key and do most of the interaction with the blockchain for you. The system of smart contracts, wallets and blockchains connected with a web browser is often referred to as "&lt;strong&gt;Web3&lt;/strong&gt;". A web3 application can interact with smart contracts, and since smart contracts are on the blockchain themselves and not hosted on a centralized server, these applications are often referred to as "&lt;strong&gt;decentralized&lt;/strong&gt;". On a decentralized system like this, the integrity of the transaction is maintained through cryptographic proof rather than faith in a centralized authority, this idea is referred to as a "&lt;strong&gt;trustless&lt;/strong&gt;" system. Some useful real-world contracts that leverage the persistence and trustless attributes of blockchain are in the Decentralized Finance (&lt;strong&gt;Defi&lt;/strong&gt;) and Non-Fungible Token (&lt;strong&gt;NFT&lt;/strong&gt;) spaces. But for today, we'll just use this technology to increment a counter :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Project and Setup Hardhat
&lt;/h2&gt;

&lt;p&gt;Let's begin by setting up a project and the Hardhat environment. This will require you to have the &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node&lt;/a&gt; server-side Javascript runtime already installed. Note that my notes below are based on my experience on a Linux server, but it shouldn't be too difficult to adapt to some other OS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a directory and node project&lt;br&gt;
&lt;code&gt;mkdir counter-test&lt;br&gt;
cd counter-test/&lt;br&gt;
npm init --yes&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add Hardhat to project&lt;br&gt;
&lt;code&gt;npm install --save-dev hardhat&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create new Hardhat project&lt;br&gt;
&lt;code&gt;npx hardhat init&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbg1mhwbjob41b5lqi4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbg1mhwbjob41b5lqi4x.png" alt=" " width="734" height="615"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove the example contract files&lt;br&gt;
&lt;code&gt;rm contracts/Lock.sol&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the Solidity contract source in a .sol file. We'll put the below in contracts/counter-test.sol&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import "hardhat/console.sol";

contract CounterTest {
        uint count;

        constructor() public {
                count = 0;
        }

        function getCount() public view returns(uint) {
                return count;
        }

        function incrementCount() public {
                count = count + 1;
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a deploy script. Place the below script in sripts/deploy.js&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We require the Hardhat Runtime Environment explicitly here. This is optional&lt;/span&gt;
&lt;span class="c1"&gt;// but useful for running the script in a standalone fashion through `node &amp;lt;script&amp;gt;`.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// You can also run a script with `npx hardhat run &amp;lt;script&amp;gt;`. If you do that, Hardhat&lt;/span&gt;
&lt;span class="c1"&gt;// will compile your contracts, add the Hardhat Runtime Environment's members to the&lt;/span&gt;
&lt;span class="c1"&gt;// global scope, and execute the script.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockedAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lock&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;hre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deployContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CounterTest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&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;Deploying CounterTest&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="c1"&gt;// We recommend this pattern to be able to use async/await everywhere&lt;/span&gt;
&lt;span class="c1"&gt;// and properly handle errors.&lt;/span&gt;
&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="err"&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Start Hardhat Node and Deploy Contract
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Edit hardhat.config.js to include localhost:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type import('hardhat/config').HardhatUserConfig */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:8545&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1337&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;hardhat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// See defaults&lt;/span&gt;
                        &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1337&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.8.27&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;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start Hardhat localhost node for testing in separate terminal. This will start a Hardhat node in the foreground and display debugging information as we communicate with our smart contract from the other terminal:&lt;br&gt;
&lt;code&gt;npx hardhat node&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
You'll get something like the below that lists a bunch of test addresses to work with that already have some test funds:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2umxfqsmd0rxzbbbse7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2umxfqsmd0rxzbbbse7.png" alt=" " width="591" height="807"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that you have a local blockchain to test with, let's go back to the other terminal and deploy our smart contract with the deploy.js script that we created earlier.&lt;br&gt;
&lt;code&gt;npx hardhat run scripts/deploy.js --network localhost&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If we monitor the Hardhat node we will see our attempt to deploy the smart contract. Note that the From address matches the public address of the first test account. Also make note that we now have a contract address (0x5fbdb2315678afecb367f032d93f642f64180aa3), we'll need this to interact with the contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwoi6v4d0uw5x4z73fp83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwoi6v4d0uw5x4z73fp83.png" alt=" " width="667" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our contract is deployed! &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One final note about deployed smart contracts for this post - The web3 version of an "API" is an ABI, or Application Binary Interface. If someone wants to create an application to interact with your contract, they will need to know what functions are available. These can easily found in the ABI JSON that was generated when you deployed, take a look at artifacts/contracts/counter-test.sol/CounterTest.json . The important part here is the JSON array for ABI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cvhqr9ndgiwhzw7zn4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cvhqr9ndgiwhzw7zn4h.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Interact With Your Contract From Hardhat Console
&lt;/h2&gt;

&lt;p&gt;Now that our contract is written and deployed on the blockchain, let's try to call some of it's functions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start the Hardhat Javascript console&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx hardhat console --network localhost&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We'll store our contract connection in the myContract variable&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const myContract = await ethers.getContractAt("CounterTest","0x5fbdb2315678afecb367f032d93f642f64180aa3")&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, let's call our getCount() function and see what the current count is:&lt;br&gt;
&lt;code&gt;await myContract.getCount()&lt;/code&gt;&lt;br&gt;
You should get "0n" if this is our first run. Ignore the n, this is just indicates the type. You can cast this to an integer or whatever type you need in code if you were using this contract in real-life.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, let's increment the counter with our incrementCount() function:&lt;br&gt;
&lt;code&gt;await myContract.incrementCount()&lt;/code&gt;&lt;br&gt;
You'll see a lot more output from this call since we are actually adding information to the blockchain. Most notably you'll see the blockHash of the transaction and the gasPrice. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call getCount() again to confirm that the counter incremented as expected. The entire interaction should look something like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npz82jtbqx8m9k58dp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npz82jtbqx8m9k58dp3.png" alt=" " width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it! We have written, deployed and called a smart contract on our simulated blockchain! To turn this into a web3 application, you can explore creating a frontend that includes the &lt;a href="https://docs.ethers.org/" rel="noopener noreferrer"&gt;ethers.js&lt;/a&gt; library to interact with your contract.&lt;/p&gt;

</description>
      <category>smartcontract</category>
      <category>polygon</category>
      <category>hardhat</category>
    </item>
    <item>
      <title>Parallax Backgrounds</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Fri, 16 Dec 2022 04:42:58 +0000</pubDate>
      <link>https://dev.to/drunnells/parallax-backgrounds-321h</link>
      <guid>https://dev.to/drunnells/parallax-backgrounds-321h</guid>
      <description>&lt;p&gt;Not a huge update this week, but I did get parallax backgrounds working and fixed some navigation weirdness:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihw9ll6amoud7io6zwos.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihw9ll6amoud7io6zwos.gif" alt=" " width="635" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did run into a problem that I'm concerned will come back to bite me a lot in the remainder of this project - The parallax background was very smooth when I tested with some preloaded images for the layers. But once I import the layers from my levels zip file, things start to get a little jerky. Looking deeper, I have issues with all of my imported images that would be solved with preloads at compile time, including sprites with sharp edges because fix_alpha_edges() doesn't happen outside of preloads(?). BUT I can't preload if these images are coming from images loaded from a zip file at runtime.&lt;/p&gt;

&lt;p&gt;Before jumping into a bunch of level creation, I want to add a couple more abilities and sprite animations along with squashing a few bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stairs are not working very good with just physics guiding the students. Maybe I can set a path for the sprites to follow and disable any kind of ability selection while on the stairs.&lt;/li&gt;
&lt;li&gt;Sprite edges are too rough for imported images.&lt;/li&gt;
&lt;li&gt;Parallax background layers are jerky.&lt;/li&gt;
&lt;li&gt;Normalmaps appear to be inverted (raised areas are embossed).&lt;/li&gt;
&lt;li&gt;Doors need animation and texture&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
    </item>
    <item>
      <title>Skeletons and Sprites!</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Fri, 09 Dec 2022 21:55:19 +0000</pubDate>
      <link>https://dev.to/drunnells/skeletons-and-sprites-2ean</link>
      <guid>https://dev.to/drunnells/skeletons-and-sprites-2ean</guid>
      <description>&lt;p&gt;When I first started working on this project I had some experience with sprite sheets. I did a quick test scene with an animated sprite using a sprite sheet and thought I understood what I needed and proceeded to work on the rest of the game leaving the sprite work for later. As it turned out, making an animated 2d sprite in a 3d game was not as trivial as I had thought. As mentioned previously, I want to just be able to feed a sprite "atlas" of some kind to the game (maybe from an NFT!) and have it just show up in the game for the random student selection when playing a level. To get there in 3d, I needed to make the Sprite3d in Godot have a viewport to a 2d scene that contained the animation. This seems to work, but hopefully it isn't too inefficient... it feels a little hackey! But very cool that Godot lets you do this! Lots of Inkscape and Gimp work this week with me trying to be an artist drawing this person. &lt;/p&gt;

&lt;p&gt;I added some brick to the camera facing part of the building so that the walls look less thin and built a simple Student Editor to my level designer: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ie5u0inf8j8pby9rvgk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ie5u0inf8j8pby9rvgk.jpg" alt=" " width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wwr9r7wbx9jnop3e1o2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wwr9r7wbx9jnop3e1o2.gif" alt=" " width="654" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can catch a glimpse of a green "skeleton" student above. This was me playing with using multiple student designs. It is so awesome to be able to make an animation with bones and then stick the texture on top so I can just draw the limbs and the animations are built into the game without a huge sprite sheet with every gesture for every character design.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
    </item>
    <item>
      <title>Textures &amp; Zoom</title>
      <dc:creator>Dustin Runnells</dc:creator>
      <pubDate>Sun, 04 Dec 2022 19:23:30 +0000</pubDate>
      <link>https://dev.to/drunnells/textures-zoom-4gjm</link>
      <guid>https://dev.to/drunnells/textures-zoom-4gjm</guid>
      <description>&lt;p&gt;Continuing to learn more than I thought that I wanted to about shaders! My initial concept of being able to just throw 5 images into walls for each room isn't going to be as simple as I had first thought. I suppose that it could be, but it looks cooler with some normalmap, metallic, roughness and emission textures. It is only complicated for me because I can't use Godot's built-in material stuff if I'm doing custom shaders to make holes in the walls. So I anticipate that I'll be spending a lot of time on the level design anyway! For now I'm looking at some stock photos of school classrooms and using Gimp to just cut out different pieces that I think will look cool in the game to make a room. I'll need to do some modeling for props like desks and stuff at some point. I also added zooming with the mouse wheel so that I could get a better look at things. The challenge for the last few days has been lighting. My shader is not behaving as expected with lights in neighboring rooms bleeding through the walls. For now I've solved this by just not having lights in each room and instead moving a single light that follows the camera position:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lcxbwc7sjakbhn9gagu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lcxbwc7sjakbhn9gagu.gif" alt=" " width="654" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the walls currently having no thickness, I think that I'll add some brick to the camera facing wall around the edges so it looks less like paper thin walls. I don't have any plans on designing the outside of the building, so I'll need to limit the camera movement to prevent anyone from going too far left or right and seeing the untextured outside of the building. Maybe I'll also add a background soon that you'll be able to see through the windows. I'll add the bus loop with some school bus models eventually as the end goal for each level.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
