DEV Community

Cover image for Part 1 - Using Claude, Claude-Dev and Aider to Build a Ticketing System
Joe Giglio, Chief Remote Officer
Joe Giglio, Chief Remote Officer

Posted on • Edited on

Part 1 - Using Claude, Claude-Dev and Aider to Build a Ticketing System

Part 2 | Part 3 | Part 4

PART 1 -

The project has been open sourced if you would like to play along at home: https://github.com/joegiglio/helpdesk-claude-dev-ai

AI is probably coming for all of us eventually. It promises to make companies “more efficient” but how far along is it right now? Can it really be used to replace software developers? Can you just pick a tech stack, tell AI what to write and put your feet up while the money rolls in?

Money

I had tinkered with the usual LLMs - Copilot, ChatGPT, Gemini, Gab and Claude. I found them all to be capable of helping to create boilerplate code, adding basic unit tests and acting as a reliable pair programmer. The answers are usually pretty good but not without error. They give a solid starting point and prevent you from having to deal with all the geniuses on Stack Overflow.

I have seen demo videos of snake games and To-Do list web apps being created using only AI prompts without code and I wanted to take it further. Having a background in help desk and ticketing software, I wanted to push AI to create a ticketing system and see how far I can take it with integrations into third party apps such as Slack and Salesforce.

So what would my AI stack be? At the moment, the buzz seems to be around Claude, paired with VS Code extensions Claude-Dev and Aider so I figured I would start there. I believe the best tool for the job is the one you are most familiar with, so I would ask AI to use a tech stack of Python, Flask, SQLite, Bootstrap and jQuery. Keeping with languages I am familiar with will help me with the inevitable debugging and maintenance.

Claude logo

Using these plug-ins requires an API key but acquiring one is not as straightforward as you might expect. You create your account and login at https://claude.ai/ but need to obtain your API key from https://console.anthropic.com/ and there is no obvious link… or at least there was none during my initial draft but now there is. :)

Once I had my API key, I needed to add credits to my account or API requests would be rejected. Luckily, Anthropic is running a $5 credit promotion which appears in your dashboard. I accepted the offer, was texted a validation code which I entered and then…nothing. I did not see any credits. I was stuck in a loop of offer/validation/acceptance and could not get the credits to appear in my account. I sent them an email and figured I would try again the next day.

The next day came and there was no reply so I just decided to purchase $5 worth of credits and get on with the show. First up, I would try Aider which runs in a VS Code terminal window. I am not crazy about the UI but configuration was easy enough, it makes Github commits after each step and it gets great reviews on Youtube so it was worth a try.

Here is the initial prompt I used. I chopped it into a couple of pieces so I could check the work as we went along. Much like people, AI works best iteratively, giving you a chance to make sure things are working as intended at each milestone and giving you an opportunity to debug and clean up.

[START FIRST PROMPT]

I would like to create simple help desk software using AI to build the product. Instructions will be given iteratively so it can be built piece by piece.

You are an expert web developer with expertise in Python and Flask. All code must be secure to prevent SQL injection and other attacks. All fields must have reasonable size limits and maxlengths. Field lengths and data types are to be protected on the client and server side.

The technical requirements are:

Python
Flask
Fast-API
Bootstrap
jQuery
We will initially use SQLite for simplicity but will move to MySQL at some point so keep that in mind.

All pages must be responsive and work well on desktop, tablet and mobile screen resolutions. The product will be API-First with all CRUD operations working through the API layer using Fast-API. This includes fields for support tickets as well as administration such as users, preferences and other settings.
Integrations with other popular products such as Slack, JIRA and Salesforce and the ability to send data to a webhook URL will be important for adoption so keep that in mind while building the core of this platform.

[END FIRST PROMPT]

[START SECOND PROMPT]

Product requirements for the first iteration:
Build a lefthand bar with the following links:
Tickets
Integrations
Workflows
Team
Settings
Each of these links will be its own files in the templates directory.

When clicking one of the entries on the left navigation bar, the right side will refresh with the necessary data. Examples:
When you click Tickets, show a list of existing cases and a link to create a new case.
When creating a new case, display the following fields:
Ticket ID (Numeric, auto generated, read only but visible)
Customer First Name (field length 25, maxlength 50)
Customer Last Name (field length 25, maxlength 50)
Customer Email (field length 25, maxlength 50)
Subject (field length 50, maxlength 100)
Priority (Dropdown with values 1-Urgent, 2-Important, 3-Normal, 4-Low)
Assignee (Dropdown with a list of dummy data for now)
Details (Large textarea, maxlength 50,000)

There should be a save button so you can save the case.
When you click Integrations on the left navigation bar, show logos and placeholder data for Slack, JIRA and Salesforce.
When you click Workflows on the left navigation bar, show a placeholder header graphic and placeholder data.
When you click Team on the left navigation bar, show a placeholder header graphic and placeholder data.

When you click Settings on the left navigation bar, show a placeholder header graphic and placeholder data.

[END SECOND PROMPT]

After running the first part of the prompt, I checked the project structure and files. I was surprised and impressed to find that it generated a modular Flask file structure, with blueprints, separate routes files and Pydantic for strong typing which I believe is required for FastAPI.

a

Out of the box, Flask allows you to quickly crank out code and it is not strict about file structure or naming conventions other than the ‘static’ folder. This allows you to get up and running without a lot of ceremony which can be a benefit or drawback depending on the project. Aider, with Claude, was taking my project right into the deep end into somewhat unfamiliar territory but I liked what I saw so far!

However, the server would not start and dependency version conflicts took about 10 attempts to fix. Each request costs money and goes against your hourly and daily quota. Yes, it gives you the wrong code and yes, you have to pay for it to clean up and may be “put on timeout” if you ask it too many times while it works through the problem.

Although the server finally started and seemed to be working, it was using Uvicorn as its web server and it was not configured to detect changes to files other than Python. This meant every time I made a change to the HTML or CSS, I had to remember to manually restart the server. I found a few possible solutions from Claude and other websites but I could not get this to work. I put that annoyance aside and would go another round the next day.

Here is the initial UI Claude generated:

b

I spent some more time on the server restart problem the next day but still had no luck so I decided to add this requirement to the project requirement prompts along with some other tweaks to fix shortcomings from the first round. I created a new folder and went another round with Aider:

[START FIRST PROMPT]

I would like to create simple help desk software using AI to build the product. Instructions will be given iteratively so it can be built piece by piece.

You are an expert web developer with expertise in Python and Flask. All code must be secure to prevent SQL injection and other attacks. All fields must have reasonable size limits and maxlengths. Field lengths and data types are to be protected on the client and server side.

The technical requirements are:

Python
Flask
Fast-API
Bootstrap
jQuery
We will initially use SQLite for simplicity but will move to MySQL at some point so keep that in mind.
I am using VS Code as my IDE. Please make sure that changes to all files such as HTML templates and CSS files trigger an automatic restart of the web server.

All pages must be responsive and work well on desktop, tablet and mobile screen resolutions. The product will be API-First with all CRUD operations working through the API layer using Fast-API. This includes fields for support tickets as well as administration such as users, preferences and other settings.
Integrations with other popular products such as Slack, JIRA and Salesforce and the ability to send data to a webhook URL will be important for adoption so keep that in mind while building the core of this platform.

Let me know how to install all the necessary dependencies.

[END FIRST PROMPT]

[START SECOND PROMPT]

Product requirements for the first iteration:
Build a lefthand bar with the following links:
Tickets
Integrations
Workflows
Team
Settings

Each of these links will be its own files in the templates directory.

When clicking one of the entries on the left navigation bar, the right side will refresh with the necessary data and the left navigation bar will still appear. Examples:
When you click Tickets, show a list of existing cases and a link to create a new case.
When creating a new case, display typical fields that are found in ticketing software. Each should have a reasonable field length and maxlength.

There should be a save button so you can save the case to the database.
When you click Integrations on the left navigation bar, show logos and placeholder data for Slack, JIRA and Salesforce.
When you click Workflows on the left navigation bar, show a placeholder header graphic and placeholder data.
When you click Team on the left navigation bar, show a placeholder header graphic and placeholder data.
When you click Settings on the left navigation bar, show a placeholder header graphic and placeholder data.

[END SECOND PROMPT]

I ended up with a similar file structure as the first attempt but the server would not start due to circular import issues, a familiar pain point for Python developers when they try to modularize their code and break pieces into separate directories:

ImportError: cannot import name 'routes' from partially initialized module 'helpdesk_app' (most likely due to a circular import) (/home/joe/Documents/programming/python/helpdesk2/helpdesk_app/__init__.py)

Aider was able to fix this and similar issues after several attempts. Finally, I could get the server to start without issue but when I tried to hit the homepage, it generated a database exception which I fed back to Aider:

When I try to hit the homepage I am getting this error. It looks like a problem with the database: sqlite3.OperationalError: no such table: tickets

Aider itself immediately crashes with:

Traceback (most recent call last):
File "/home/joe/Documents/programming/python/helpdesk1/.venv/bin/aider", line 8, in <module>
sys.exit(main())
^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/main.py", line 620, in main
coder.run()
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 685, in run
self.run_one(user_message, preproc)
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 723, in run_one
list(self.send_message(message))
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 945, in send_message

messages = self.format_messages()
^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 904, in format_messages
messages_tokens = self.main_model.token_count(messages)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/models.py", line 532, in token_count
return litellm.token_counter(model=self.name, messages=messages)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/litellm/utils.py", line 1987, in token_counter
num_tokens += calculage_img_tokens(
^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/litellm/utils.py", line 1834, in calculage_img_tokens
resized_width, resized_height = resize_image_high_res(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/litellm/utils.py", line 1751, in resize_image_high_res
if width <= 768 and height <= 768:
^^^^^^^^^^^^
TypeError: '<=' not supported between instances of 'NoneType' and 'int'
(.venv) joe@mint:~/Documents/programming/python/helpdesk1$ aider
Aider v0.49.1
Models: claude-3-5-sonnet-20240620 with diff edit format, weak model claude-3-haiku-20240307
Git repo: .git with 21 files
Repo-map: using 1024 tokens
VSCode terminal detected, pretty output has been disabled.

Trying again:

It looks like the database tables were never created or initialized. Can you create code to do so?

Traceback (most recent call last):
File "/home/joe/Documents/programming/python/helpdesk1/.venv/bin/aider", line 8, in <module>
sys.exit(main())
^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/main.py", line 620, in main
coder.run()
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 685, in run
self.run_one(user_message, preproc)
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 723, in run_one
list(self.send_message(message))
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 945, in send_message
messages = self.format_messages()
^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 892, in format_messages
messages += self.get_files_messages()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 592, in get_files_messages
repo_content = self.get_repo_map()
^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/coders/base_coder.py", line 564, in get_repo_map
repo_content = self.repo_map.get_repo_map(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/repomap.py", line 105, in get_repo_map
files_listing = self.get_ranked_tags_map(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/repomap.py", line 448, in get_ranked_tags_map
num_tokens = self.token_count(tree)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/repomap.py", line 69, in token_count
return self.main_model.token_count(text)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/models.py", line 542, in token_count
return len(self.tokenizer(msgs))
^^^^^^^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/models.py", line 528, in tokenizer
return litellm.encode(model=self.name, text=text)
^^^^^^^^^^^^^^
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/llm.py", line 20, in __getattr__
self._load_litellm()
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/aider/llm.py", line 27, in _load_litellm
self._lazy_module = importlib.import_module("litellm")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 995, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/litellm/__init__.py", line 20, in <module>
from litellm.proxy._types import (
File "/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/litellm/proxy/_types.py", line 10, in <module>
from pydantic import BaseModel, ConfigDict, Extra, Field, Json, model_validator
ImportError: cannot import name 'model_validator' from 'pydantic' (/home/joe/Documents/programming/python/helpdesk1/.venv/lib/python3.12/site-packages/pydantic/__init__.py). Did you mean: 'root_validator'?

Another exception - something Pydantic related? Way out of my league...

c

I was stuck and STILL had the web server restart issue. It was discouraging but I decided to simplify the initial application by removing the Fast-API requirements and sticking with a simpler Flask structure to see if I can get the basics to work and then push into the more advanced stuff iteratively.

These are the prompts I used next. There are some differences including instructions around the virtual directory, removal of FastAPI and the generating of a .gitignore file.

[START FIRST PROMPT]

I would like to create simple help desk software using AI to build the product. Instructions will be given iteratively so it can be built piece by piece.

You are an expert web developer with expertise in Python and Flask. All code must be secure to prevent SQL injection and other attacks. All fields must have reasonable size limits and maxlengths. Field lengths and data types are to be protected on the client and server side.

The technical requirements are:
Python
Flask
Bootstrap
jQuery

We will initially use SQLite for simplicity but will move to MySQL at some point so keep that in mind. Be sure to initialize the database.

Let me know how to install all the necessary dependencies. The virtual environment has already been created and is active. The directory name is “venv”.

Generate an appropriate .gitignore file for the project and include the venv directory in it.
I am using VS Code as my IDE. Please make sure that changes to all files such as HTML templates and CSS files trigger an automatic restart of the web server.

All pages must be responsive and work well on desktop, tablet and mobile screen resolutions.
Integrations with other popular products such as Slack, JIRA and Salesforce and the ability to send data to a webhook URL will be important for adoption so keep that in mind while building the core of this platform.

[END FIRST PROMPT]

[START SECOND PROMPT]

Product requirements for the first iteration:
Build a lefthand bar with the following links:
Tickets
Integrations
Workflows
Team
Settings
Each of these links will be its own files in the templates directory.

When clicking one of the entries on the left navigation bar, the right side will refresh with the necessary data and the left navigation bar will still appear. Examples:
When you click Tickets, show a list of existing cases and a link to create a new case.

When creating a new case, display typical fields that are found in ticketing software. Each should have a reasonable field length and maxlength.

There should be a save button so you can save the case to the database.
When you click Integrations on the left navigation bar, show logos and placeholder data for Slack, JIRA and Salesforce.
When you click Workflows on the left navigation bar, show a placeholder header graphic and placeholder data.
When you click Team on the left navigation bar, show a placeholder header graphic and placeholder data.

When you click Settings on the left navigation bar, show a placeholder header graphic and placeholder data.

[END SECOND PROMPT]

The server started right up and the file structure was much simpler:

d

Everything seemed to work OK and I was also impressed with what Claude did with the skeleton of the Integrations page:

e

Integrations Page

I noticed that the .gitignore file was generated but it was not excluding the “.venv” directory which was my mistake. I originally instructed it to look for a “venv” directory instead of a “.venv” directory. AI is only as smart as what you feed it and it can’t read your mind. Yet.

I then prompted:

The basic frameworks seems to be working OK but I would like to add more fields for each ticket. Please add more field types that would be typical of a helpdesk ticketing application. Each field must be an appropriate length with maxsize enforced on the client and server side.
Also create the ability to edit each ticket from the tickets page. Clicking the ticket ID field would be the logical place to add this functionality.

This worked great and I thanked Claude and prompted him to take a break but I got this error message:

429 {"type":"error","error":{"type":"rate_limit_error","message":"Number of request tokens has exceeded your per-minute rate limit (https://docs.anthropic.com/en/api/rate-limits); see the response headers for current usage. Please reduce the prompt length or the maximum tokens requested, or try again later. You may also contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase."}}

OK then… I let it sit for a while and wanted to see if Claude could add “ticket delete” functionality. I created a new branch and prompted:

Please now add the ability to delete a ticket. For now, let’s just mark a ticket as deleted and not physically delete it from the database. Add a delete option to each ticket in the listing of all tickets. Use an X icon. Confirm deletion. Hide deleted tickets from the list of all tickets. We will soon build a view to show only deleted tickets. Be sure to update the database schemas as appropriate.

This returned another rate limit error:

429 {"type":"error","error":{"type":"rate_limit_error","message":"Number of request tokens has exceeded your daily rate limit (https://docs.anthropic.com/en/api/rate-limits); see the response headers for current usage. Please reduce the prompt length or the maximum tokens requested, or try again later. You may also contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase."}}

Womp, womp… I guess Claude will get the rest of the day off and I will see how far I can push it tomorrow.

The next day, I fed the same prompt and it returned a well thought out response to show that it understood the technical requirements:

To accomplish this task, we'll need to make several changes to our existing codebase. Let's break it down into steps and use the available tools to implement these changes.
We need to update the database schema to add a 'deleted' column to the tickets table.
We need to modify the app.py file to add a new route for deleting tickets.
We need to update the tickets.html template to add a delete button with an X icon for each ticket.
We need to modify the existing route that lists all tickets to exclude deleted tickets.
Let's start by examining the current state of our app.py file and the tickets.html template.

After showing me the proposed change step-by-step, Claude reported back that it was done!

Task Completed
The ability to delete tickets has been successfully implemented. Here's a summary of the changes:
Updated the Ticket model in app.py to include a 'deleted' column (Boolean, default False).
Modified the tickets route to only show non-deleted tickets using Ticket.query.filter_by(deleted=False).all().
Added a new route /tickets/<int:id>/delete for soft deleting tickets.
Updated the tickets.html template to include a delete button with an X icon for each ticket.
Added confirmation dialog before deleting a ticket.
Ran database migrations to apply the schema changes.
These changes allow users to mark tickets as deleted without physically removing them from the database. Deleted tickets are hidden from the main ticket list, and we can easily implement a view to show only deleted tickets in the future.
To see the changes in action, you can run the Flask application and navigate to the tickets page. You should see the new delete buttons (X icons) next to each ticket. Clicking on a delete button will prompt for confirmation before marking the ticket as deleted.

I saw the new column in the database but now none of my tickets were appearing in the list:

Ticket list

Newly created tickets were appearing and this is the code generated to query for non deleted tickets:

def tickets():
tickets = Ticket.query.filter_by(deleted=False).all() # Only show non-deleted tickets
return render_template('tickets.html', tickets=tickets)

Based on my experience, this is probably a zero vs. null issue. I asked Claude to fix it:

None of the existing tickets are appearing in the ticket list but newly created tickets do appear. The query for getting all tickets may be incorrect.

The response:

I apologize for the oversight. It seems there's an issue with the query for retrieving tickets. Let's fix this problem by adjusting the query in the app.py file. We'll modify the tickets route to correctly display existing tickets while still hiding the deleted ones.

Claude updated the code to:

def tickets():
tickets = Ticket.query.filter((Ticket.deleted == False) | (Ticket.deleted == None)).all() # Show non-deleted tickets and tickets without the 'deleted' attribute
return render_template('tickets.html', tickets=tickets)

…and now it works as intended with a new column and deletion confirmation:

Delete column

I am not too crazy about the delete icon but I am just about out of Claude credits. I will continue on this journey for the next part of this journal.

SIDE NOTE - While iterating on this project, I somehow ended up with an instance that requires user login for all of the pages. I did not explicitly ask for this and I am not sure how we ended up here. I had to reboot my system and lost all my Aider / Claude history and I am not sure if it can be retrieved. However, the first check in for the Master branch does show a “flask_login” import, followed by a bunch of fixes and refactors. Those refactors added a @login_required decorator to every page.

After all those fixes and refactors, each page of the UI is asking for a login but there is no mechanism for creating an account. I will need this feature at some point but not yet. Now it is just in the way. This goes to show that you should not blindly accept the code without reviewing it and you really need to understand what you are looking at. Claude took creative liberties and added a feature I did not ask for.

Code

Login

This was an interesting exercise for me and hopefully you found it insightful. I found that Claude has the same battles I would have once I push beyond a basic Flask setup - issues with circular imports, dependency version hell, etc. New day, new tool, same old problems.

Just like any of the “no code tools”, the audience that will benefit the most are the people who have had experience writing code from line one, understand programming concepts, are able to clearly explain requirements in the written form and have troubleshooting experience in the tech stack at hand.

I will be pushing this project further iteratively to see how far I can take it.

Next up: Integrations!

Part 2

Part 3 | Part 4


ABOUT THE AUTHOR

Joe Giglio is a 25+ year veteran of the software industry. In his career, he has worked for a local dial-up ISP, startups of all sizes and a well established, slow moving enterprise. There were important lessons learned along every stop.

His recent projects include:

Joe’s guiding principles are: Remote first, quality driven, customer champion and lifelong learning. He was an introvert and practiced social distancing long before it was cool!

He currently resides in North Carolina and is always interested in new opportunities, meeting quality people and learning about exciting projects.

Contact Joe Giglio: joe -AT- joegiglio.org | Twitter | LinkedIn


Some images from Unsplash

Top comments (3)

Collapse
 
lindamiles profile image
Linda

Great read!! Your article resonated with me, so I am going to include it tomorrow's issue of my daily tech newsletter, dailysandbox.com

Collapse
 
chiefremote profile image
Joe Giglio, Chief Remote Officer

Thanks, Art! Check out Part 2 that I just uploaded last night. dev.to/chiefremote/part-2-using-cl...

Collapse
 
lindamiles profile image
Linda

Thanks Joe!! The second part will be featured in tomorrow's edition :)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.