databases

## Table of contents

## vector database comparison: Weaviate vs Milvus

This page contains a detailed comparison of the Weaviate and Milvus vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Weaviate | Milvus | | ---------| -------------| -------------| | Local Deployment | ✅ | ✅ | | Cloud Deployment | ✅ | ❌ | | On - Premises Deployment | ✅ | ✅ |

## Scalability | Feature | Weaviate | Milvus | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ✅ | | Vertical Scaling | ✅ | ❌ | | Distributed Architecture | ✅ | ✅ |

## Data Management | Feature | Weaviate | Milvus | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ✅ | ✅ |

## Security | Feature | Weaviate | Milvus | | ---------| -------------| -------------| | Authentication | ✅ | ✅ | | Data Encryption | ✅ | ❌ | | Access Control | ✅ | ✅ |

## Vector Similarity Search | Feature | Weaviate | Milvus | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean, Jaccard | Euclidean, Cosine, Jaccard | | ANN Algorithms | HNSW, Beam Search | IVF, HNSW, Flat | | Filtering | ✅ | ✅ | | Post-Processing | ✅ | ✅ |

## Integration and API | Feature | Weaviate | Milvus | |---------|-------------|-------------| | Language SDKs | Python, Go, JavaScript | Python, Java, Go | | REST API | ✅ | ✅ | | GraphQL API | ✅ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Weaviate | Milvus | |---------|-------------|-------------| | Open-Source | ✅ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Weaviate | Milvus | |---------|-------------|-------------| | Free Tier | ❌ | ✅ | | Pay-as-you-go | ❌ | ❌ | | Enterprise Plans | ✅ | ✅ |

copilot

copilot

review

 GitHub Copilot has immense potential, but continues to underwhelm

When I signed up to try out GitHub Copilot, I was delighted to find that GitHub gifted me a free license to use it based on my being an [active open-source developer](https://github.com/zackproser).

Initially, I configured it for use with Neovim, my preferred code editor, but have also used it with VSCode. Here's my unvarnished opinion after giving it several chances over the course of several months.

## The potential is there but the performance is not

GitHub Copilot struggles to make relevant and high-quality code completion suggestions. I don't do comment-driven development, where you specify what you want in a large block of code comments and cross your fingers that Copilot can figure it out and generate your desired code correctly, but even when I did this to put Copilot through its paces, it still underwhelmed me.

As other developers have noted, unfortunately Copilot manages best when you've already defined the overall structure of your current file, and already have a similar function that Copilot can reference.

In these cases, Copilot can usually be trusted to handle completing the boilerplate code for you.

## ChatGPT4 upgrade, product page teasers and developer rage

 GitHub Copilot X might be the promised land, but the launch and teaser has been handled poorly

For many months now, GitHub's Copilot product page has teased the fact that the next version of GitHub Copilot, known currently as Copilot X, will use ChatGPT4 under the hood and will handle way more than code completions.

Copilot X will also help you with pull request descriptions, adding tests to existing code bases, and it will have a chat feature that allows you to ask detailed questions about your codebase and theoretically get back useful answers.

It's worth noting that Sourcegraph's Cody has been able to do this (albeit with some bugs) for many months now thanks to its powerful approach of marrying graph-based knowledge of your codebase with embeddings (vectors) of your code files which allows its supporting large language model (LLM), Anthropic's Claude, to return useful responses about your code for your natural language queries.

The main axe I have to grind with GitHub's product team is the level of vagueness and "I guess we'll see!" that their product page has communicated to developers who might otherwise be interested in giving Copilot X a spin.

One of the FAQ's is about price and upgrades for GitHub Copilot base model users. Will Copilot X be free? Will it cost a premium subscription? "Who knows! We're still trying to figure that out ourselves".

The sign-up and waiting list user experience has also been deeply lacking, because apparently each of Copilot X's main features: pull request description generation, test generation, chat, etc are separate waiting lists that you need to sign-up for and wait on individually. This seems like a giant miss.

## There are open-source and free competitors who continue to build developer mindshare

Meanwhile, competitors such as [codeium](/blog/codeium-review) have been far more transparent with their developer audience and have been working well for many users the entire time that Copilot X has been inscrutable and vague about whether it will even be available to individual developers or only accessible to those at companies large enough to foot the bill for a team license with multiple developer seats.

Codeium is not the only horse in town. Many developers, myself included, are still deriving tremendous benefit and acceleration from keeping a browser tab to OpenAI's ChatGPT4 open as we code, for talking through architecturaly decisions, generating boilerplate code and for assistance debugging complex stack traces and TypeScript errors, to name a few use cases.

In the long term, developer experience and UX will win the game, and developers will coalesce around the tools that most reliably deliver them acceleration, peace of mind, and enhanced abilities to tackle additional scope and more ambitious projects. GitHub Copilot X would do well to take a more open approach, state their intentions clearly and be transparent about their plans for developer experience, because developers in the market for AI-assisted tooling are falling in love with their many competitors in the meantime.

I built [a chat with my blog experience](/chat) into my site, allowing visitors to ask questions of my writing.

Here's a quick demo of it in action - or [you can try it out yourself](/chat):

My solution recommends related blog posts that were used to answer the question as the LLM responds. This is [Retrieval Augmented Generation](https://pinecone.io/learn/retrieval-augmented-generation) with citations.

And in this blog post, I'm giving you everything you need to build your own similar experience:

the ingest and data processing code in a Jupyter Notebook, so you can convert your blog to a knowledgebase

the server

side API route code that handles embeddings, context retrieval via vector search, and chat

the client

side chat interface that [you can play with here](/chat)

Best of all, this site is [completely open-source](https://github.com/zackproser/portfolio), so you can view and borrow my implementation.

## Table of contents

## Architecture and data flow

Here's a flowchart describing how the feature works end to end.

Let's talk through it from the user's perspective. They ask a question on my client-side chat interface. Their question is sent to my `/api/chat` route.

The chat route first converts the user's natural language query to embeddings, and then performs a vector search against Pinecone.

Embeddings or vectors are lists of floating point numbers that represent semantic meaning and the relationships between entities in data in a format that is efficient for machines and Large Language Models to parse.

For example, in the following log statements, you can see the user's query, 'How can I become a better developer? ', and the query embedding which is an array of floats representing that sentence.

 Note the length of this array is `3072`. This is the dimensionality (or number of dimensions) expressed by the embedding model, which is `text-embedding-3-large`, (which I chose over the cheaper text-embedding-3-small model because my primary concern is accuracy).

Since the embedding model I'm using for this application outputs 3072 dimensions, when I created my Pinecone index, I also set it to 3072 dimensions as well.

Let's look at how this works in the server-side API route.

## Server side

We'll step through the server-side API route section by section, building up to the complete route at the end.

### Retrieval phase

When the `/api/chat` route receives a request, I pop the latest user message off the request body and hand it to my context retrieval service:

Here's what the context service looks like:

The `getContext` function's job is to convert the user's message to vectors and retrieve the most relevant items from Pinecone.

It is a wrapper around the `getEmbeddings` and `getMatchesFromEmbeddings` functions, which are also defined in separate 'services' files.

Here's the `getEmbeddings` function, which is a thin wrapper around OpenAI's embeddings endpoint:

So far, we've received the user's query and converted it into a `query vector` that we can send into Pinecone's vector database for similarity search.

The `getMatchesFromEmbeddings` function demonstrates how we use Pinecone to execute our query and return the nearest neighbors:

 // Imagine metadata like a JavaScript object { "text": "In this article I reflect back on the year...", "source": "src/app/blog/2023-wins/page.mdx" } javascript { id: 'b10c8904-3cff-4fc5-86fc-eec5b1517dab', score: 0.826505, values: [ [length]: 0 ], sparseValues: undefined, metadata: { source: 'portfolio/src/app/blog/data-driven-pages-next-js/page.mdx', text: 'While the full script is quite long and complex, breaking it down into logical sections helps us focus on the key takeaways:\n' + '\n' + '1. Generating data-driven pages with Next.js allows us to create rich, informative content that is easy to update and maintain over time. 1. By separating the data (in this case, the categories and tools) from the presentation logic, we can create a flexible and reusable system for generating pages based on that data. 1. Using a script to generate the page content allows us to focus on the high-level structure and layout of the page, while still providing the ability to customize and tweak individual sections as needed. 1. By automating the process of generating and saving the page content, we can save time and reduce the risk of errors or inconsistencies.\n' + '\n' + 'While the initial setup and scripting can be complex, the benefits in terms of time savings, consistency, and maintainability are well worth the effort.'

} }, { id: 'b78bcb7c-c1a6-48a3-ac6b-ab58263b6ac1', score: 0.825771391, values: [ [length]: 0 ], sparseValues: undefined, metadata: { source: 'portfolio/src/app/blog/run-your-own-tech-blog/page.mdx', text: 'I wanted the ability to author code blocks of any kind directly in my post and I wanted outstanding image support with all the lazy-loading, performance optimized, responsive image goodness that Next.js bakes into its easy to use `` component.\n' + '\n' + 'I also knew I wanted to host my site on Vercel and that I wanted my site to be completely static once built, with serverless functions to handle things like form submissions so that I could customize my own email list tie-in and have more excuses to learn the Next.js framework and Vercel platform well.\n' + '\n' + ' Running your own tech blog is a great excuse to go deep on web performance monitoring and pagespeed optimization.' } }, javascript // Create a new set for blog urls let blogUrls = new Set()

let docs: string[] = [];

(context as PineconeRecord[]).forEach(match => { const source = (match.metadata as Metadata).source // Ensure source is a blog url, meaning it contains the path src/app/blog if (!source.includes('src/app/blog')) return blogUrls.add((match.metadata as Metadata).source); docs.push((match.metadata as Metadata).text); });

let relatedBlogPosts: ArticleWithSlug[] = []

// Loop through all the blog urls and get the metadata for each for (const blogUrl of blogUrls) { const blogPath = path.basename(blogUrl.replace('page.mdx', '')) const localBlogPath = `${blogPath}/page.mdx` const { slug, ...metadata } = await importArticleMetadata(localBlogPath); relatedBlogPosts.push({ slug, ...metadata }); } javascript

const serializedArticles = Buffer.from(

JSON.stringify(relatedBlogPosts)

).toString('base64')

return new StreamingTextResponse(result.toAIStream(), { headers: { "x-sources": serializedArticles } }); javascript

// Join all the chunks of text together, truncate to the maximum number of tokens, and return the result const contextText = docs.join("\n").substring(0, 3000)

const prompt = ` Zachary Proser is a Staff software engineer, open - source maintainer and technical writer Zachary Proser's traits include expert knowledge, helpfulness, cleverness, and articulateness. Zachary Proser is a well - behaved and well - mannered individual. Zachary Proser is always friendly, kind, and inspiring, and he is eager to provide vivid and thoughtful responses to the user. Zachary Proser is a Staff Developer Advocate at Pinecone.io, the leader in vector storage. Zachary Proser builds and maintains open source applications, Jupyter Notebooks, and distributed systems in AWS START CONTEXT BLOCK ${contextText} END OF CONTEXT BLOCK Zachary will take into account any CONTEXT BLOCK that is provided in a conversation. If the context does not provide the answer to question, Zachary will say, "I'm sorry, but I don't know the answer to that question". Zachary will not apologize for previous responses, but instead will indicate new information was gained. Zachary will not invent anything that is not drawn directly from the context. Zachary will not engage in any defamatory, overly negative, controversial, political or potentially offense conversations. `;

const result = await streamText({ model: openai('gpt-4o'), system: prompt, prompt: lastMessage.content, });

javascript

'use client';

...

const { messages, input, setInput, handleInputChange, handleSubmit } = useChat({ onResponse(response) { const sourcesHeader = response.headers.get('x-sources'); const parsedArticles: ArticleWithSlug[] = sourcesHeader ? (JSON.parse(atob(sourcesHeader as string)) as ArticleWithSlug[]) : []; setArticles(parsedArticles); setIsLoading(false); }, headers: {}, onFinish() { // Log the user's question gtag("event", "chat_question", { event_category: "chat", event_label: input, }); } }); ... javascript

// The questions are defined as an array of strings const prepopulatedQuestions = [ "What is the programming bug? ", "Why do you love Next.js so much? ", "What do you do at Pinecone? ", "How can I become a better developer? ", "What is ggshield and why is it important?" ];

...

// The handler for clicking one of the pre-canned question buttons const handlePrepopulatedQuestion = (question: string) => { handleInputChange({ target: { value: question, }, } as React.ChangeEvent);

gtag("event", "chat_use_precanned_question", {

event_category: "chat",

event_label: question,

});

setIsLoading(true); // Set loading state here to indicate submission is processing

const customSubmitEvent = { preventDefault: () => { }, } as unknown as React.FormEvent;

// Submit immediately after updating the input handleSubmit(customSubmitEvent); }; javascript

'use client';

const prepopulatedQuestions = [ "What is the programming bug? ", "Why do you love Next.js so much? ", "What do you do at Pinecone? ", "How can I become a better developer? ", "What is ggshield and why is it important?" ];

const { messages, input, setInput, handleInputChange, handleSubmit } = useChat({ onResponse(response) { const sourcesHeader = response.headers.get('x-sources'); const parsedArticles: ArticleWithSlug[] = sourcesHeader ? (JSON.parse(atob(sourcesHeader as string)) as ArticleWithSlug[]) : []; console.log(`parsedArticle %o`, parsedArticles); setArticles(parsedArticles); setIsLoading(false); }, headers: {}, onFinish() { // Log the user's question gtag("event", "chat_question", { event_category: "chat", event_label: input, }); } });

const userFormSubmit = (e: React.FormEvent) => { setIsLoading(true); // Set loading state here handleSubmit(e); };

const handlePrepopulatedQuestion = (question: string) => { handleInputChange({ target: { value: question, }, } as React.ChangeEvent);

gtag("event", "chat_use_precanned_question", {

event_category: "chat",

event_label: question,

});

setIsLoading(true); // Set loading state here to indicate submission is processing

const customSubmitEvent = { preventDefault: () => { }, } as unknown as React.FormEvent;

// Submit immediately after updating the input handleSubmit(customSubmitEvent); };

return (  {isLoading && ()}   {messages.map((m) => (   {m.role === 'user' ?

'You: ' : "The Ghost of Zachary Proser's Writing: "}  {m.content}  ))}   {Array.isArray(articles) && (articles.length > 0) && (  Related Posts {(articles as ArticleWithSlug[]).map((article) => (  ))}  )}    Example Questions: Double-click to ask one of these questions, or type your own below and hit enter.  {prepopulatedQuestions.map((question, index) => (  handlePrepopulatedQuestion(question)} > {question}  ))}       ); } javascript

// Allow this serverless function to run for up to 5 minutes export const maxDuration = 300;

// Get the last message const lastMessage = messages[messages.length - 1]

// Get the context from the last message const context = await getContext(lastMessage.content, '', 3000, 0.8, false)

// Create a new set for blog urls let blogUrls = new Set()

let docs: string[] = [];

(context as PineconeRecord[]).forEach(match => { const source = (match.metadata as Metadata).source // Ensure source is a blog url, meaning it contains the path src/app/blog if (!source.includes('src/app/blog')) return blogUrls.add((match.metadata as Metadata).source); docs.push((match.metadata as Metadata).text); });

let relatedBlogPosts: ArticleWithSlug[] = []

// Loop through all the blog urls and get the metadata for each for (const blogUrl of blogUrls) { const blogPath = path.basename(blogUrl.replace('page.mdx', '')) const localBlogPath = `${blogPath}/page.mdx` const { slug, ...metadata } = await importArticleMetadata(localBlogPath); relatedBlogPosts.push({ slug, ...metadata }); } // Join all the chunks of text together, truncate to the maximum number of tokens, and return the result const contextText = docs.join("\n").substring(0, 3000)

const prompt = ` Zachary Proser is a Staff developer, open - source maintainer and technical writer Zachary Proser's traits include expert knowledge, helpfulness, cleverness, and articulateness. Zachary Proser is a well - behaved and well - mannered individual. Zachary Proser is always friendly, kind, and inspiring, and he is eager to provide vivid and thoughtful responses to the user. Zachary Proser is a Staff Developer Advocate at Pinecone.io, the leader in vector storage. Zachary Proser builds and maintains open source applications, Jupyter Notebooks, and distributed systems in AWS START CONTEXT BLOCK ${contextText} END OF CONTEXT BLOCK Zachary will take into account any CONTEXT BLOCK that is provided in a conversation. If the context does not provide the answer to question, Zachary will say, "I'm sorry, but I don't know the answer to that question". Zachary will not apologize for previous responses, but instead will indicated new information was gained. Zachary will not invent anything that is not drawn directly from the context. Zachary will not engage in any defamatory, overly negative, controversial, political or potentially offense conversations. `;

const result = await streamText({ model: openai('gpt-4o'), system: prompt, prompt: lastMessage.content, });

const serializedArticles = Buffer.from(

JSON.stringify(relatedBlogPosts)

).toString('base64')

return new StreamingTextResponse(result.toAIStream(), { headers: { "x-sources": serializedArticles } }); } python

# Clone my repository which contains my site # and all the *.MDX files comprising my blog !git clone https://github.com/zackproser/portfolio.git

# Pip install all dependencies !pip install langchain_community langchain_pinecone langchain_openai unstructured langchainhub langchain-text-splitters

# Import packages

from langchain_pinecone import PineconeVectorStore

from langchain_openai import OpenAIEmbeddings

from langchain_community.document_loaders import DirectoryLoader

from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_core.output_parsers import StrOutputParser

from langchain_core.runnables import RunnablePassthrough

# Use LangChain's DirectoryLoader to grab all my MDX files # across all subdirectories in my portfolio project. Use # multi-threading for efficiency and show progress loader = DirectoryLoader('portfolio', glob="**/*.mdx", show_progress=True, use_multithreading=True)

docs = loader.load()

docs

python from google.colab import userdata # Set the API keys os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY') os.environ['PINECONE_API_KEY'] = userdata.get('PINECONE_API_KEY')

# Assuming you've already imported necessary libraries and blog_posts is populated as above

# Initialize embeddings and the vector store embeddings = OpenAIEmbeddings( model="text-embedding-3-large" )

index_name = "zack

portfolio"

# Split the documents into chunks text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0) split_docs = text_splitter.split_documents(docs)

# Create a vector store for the documents using the specified embeddings vectorstore = PineconeVectorStore.from_documents(split_docs, embeddings, index_name=index_name)

# Ask a query that is likely to score a hit against your corpus of text or data # In my case, I have a blog post where I talk about "the programming bug" query = "What is the programming bug?" vectorstore.similarity_search(query) python # Pinecone Index sanity checks from pinecone import Pinecone, ServerlessSpec

# Initialize Pinecone client

pc = Pinecone(api_key=os.environ.get('PINECONE_API_KEY'))

# Set the name of your Pinecone Index here index_name = 'zack-portfolio'

index = pc.Index(index_name)

# This sanity check call should return stats for your Pinecone index, such as: # {'dimension: 1536, #  'index_fullness': 0.0, #  'namespaces': {'', {'vector_count': 862}}, #  'total_vector_count': 862} # index.describe_index_stats() python # Query the Pinecone index for related documents query = "What is the programming bug?"

embeddings =  OpenAIEmbeddings(

model="text

embedding

3

large"

)

vectorstore = PineconeVectorStore(embedding=embeddings, index_name=index_name)

vectorstore.similarity_search(query) python name: Upsert embeddings for changed MDX files

on:

push:

branches:

main

jobs: changed_files: runs-on: ubuntu-latest name: Process Changed Blog Embeddings

steps: - uses: actions/checkout@v4 with: fetch-depth: 0  # Ensures a full clone of the repository

name: Get changed files id: changed

files uses: tj

actions/changed

files@v44

name: List all changed files run: | echo "Changed MDX Files:" CHANGED_MDX_FILES=$(echo "${{ steps.changed

files.outputs.all_changed_files }}" | grep '\.mdx$') echo "$CHANGED_MDX_FILES" echo "CHANGED_MDX_FILES> $GITHUB_ENV echo "$CHANGED_MDX_FILES" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV

name: Set API keys from secrets run: | echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> $GITHUB_ENV echo "PINECONE_API_KEY=${{ secrets.PINECONE_API_KEY }}" >> $GITHUB_ENV

name: Install dependencies if: env.CHANGED_MDX_FILES run: | pip install langchain_community langchain_pinecone langchain_openai langchain unstructured langchainhub

name: Process and upsert blog embeddings if changed if: env.CHANGED_MDX_FILES run: | python

c " import os from langchain_pinecone import PineconeVectorStore from langchain_openai import OpenAIEmbeddings from langchain.docstore.document import Document

# Manually load changed documents changed_files = os.getenv('CHANGED_MDX_FILES').split() docs = [Document(page_content=open(file, 'r').read(), metadata={'source': 'local', 'name': file}) for file in changed_files if file.endswith('.mdx')]

# Initialize embeddings and vector store embeddings = OpenAIEmbeddings(model='text-embedding-3-large') index_name = 'zack-portfolio-3072' vectorstore = PineconeVectorStore(embedding=embeddings, index_name=index_name vectorstore.add_documents(docs) "

name: Verify and log vector store status if: env.CHANGED_MDX_FILES run: | python

c " import os from pinecone import Pinecone pc = Pinecone(api_key=os.environ['PINECONE_API_KEY']) index = pc.Index('zack

portfolio

3072') print(index.describe_index_stats()) " ```

That's it for now. Thanks for reading! If you were helped in any way by this post or found it interesting, please leave a comment or like below or share it with a friend. 🙇

data

driven

I've begun experimenting with building some of my blog posts - especially those that are heavy on data, tables, comparisons and multi-dimensional considerations - using scripts, JSON and home-brewed schemas.

## Table of contents

## What are data

driven pages?

I'm using this phrase to describe pages or experiences served up from your Next.js project that you **compile** rather than edit.

Whereas you might edit a static blog post to add new information, with a data-driven page you would update the data-source and then run the associated build process, resulting in a web page you serve to your users.

## Why build data driven pages?

In short, data driven pages make it easier to maintain richer and more information-dense experiences on the web.

Here's a couple of reasons I like this pattern:

1. There is **more upfront work** to do than just writing a new MDX file for your next post, but once the build script is stable, it's much **quicker to iterate** (Boyd's Law) 2. By iterating on the core data model expressed in JSON, you can quickly add rich new features and visualizations to the page such as additional tables and charts 3. If you have multiple subpages that all follow a similar pattern, such as side by side product review, running a script one time is a lot faster than making updates across multiple files 4. You can hook your build scripts either into npm's `prebuild` hook, which runs before `npm run build` is executed, or to the `pnpm` build target, so that your data driven pages are freshly rebuilt with no additional effort on your part 5. This pattern is a much more sane way to handle data that changes frequently or a set of data that has new members frequently. In other words, if you constantly have to add Product or Review X to your site, would you rather manually re-create HTML sections by hand or add a new object to your JSON? 6. You can drive more than one experience from a single data source: think a landing page backed by several detail pages for products, reviews, job postings, etc.

## How it works

### The data

I define my data as JSON and store it in the root of my project in a new folder.

For example, here's an object that defines GitHub's Copilot AI-assisted developer tool for my [giant AI-assisted dev tool comparison post](/blog/ai-assisted-dev-tools-compared):

As you can see, the JSON defines every property and value I need to render GitHub's Copilot in a comparison table or other visualization.

### The script

The script's job is to iterate over the JSON data and produce the final post, complete with any visualizations, text, images or other content.

The full script is relatively long. You can [read the full script in version control](https://github.com/zackproser/portfolio/blob/main/scripts/create-ai-assisted-dev-tools-comparison-post.js), but in the next sections I'll highlight some of the more interesting parts.

### Generating the Post Content

One of the most important parts of the script is the `generatePostContent` function, which takes the categories and tools data and generates the full content of the blog post in markdown format. Here's a simplified version of that function:

This function generates the full markdown content of the blog post, including the metadata, introduction, table of contents, tool table, and category sections.

By breaking this out into a separate function, we can focus on the high-level structure of the post without getting bogged down in the details of how each section is generated.

### Writing the Generated Page to a File

Another key part of the script is the code that writes the generated page content to a file in the correct location. Here's what that looks like:

This code does a few important things:

1. It determines the correct directory and filename for the generated page based on the project structure. 1. It checks if the file already exists and, if so, extracts the existing date from the page's metadata. This allows us to preserve the original publication date if we're regenerating the page. 1. It generates the full page content using the `generatePostContent` function. 1. It creates the directory if it doesn't already exist. 1. It writes the generated content to the file.

## Automating the Build Process with npm and pnpm

One of the key benefits of using a script to generate data-driven pages is that we can automate the build process to ensure that the latest content is always available.

Let's take a closer look at how we can use npm and pnpm to run our script automatically before each build.

### Using npm run prebuild

In the package.json file for our Next.js project, we can define a "prebuild" script that will run automatically before the main "build" script:

With this setup, whenever we run `npm run build` to build our Next.js project, the prebuild script will run first, executing our page generation script and ensuring that the latest content is available. Using pnpm build

If you're using pnpm instead of npm, then the concept of a "prebuild" script no longer applies, unless you enable the `enable-pre-post-scripts` option in your `.npmrc` file as [noted here](https://pnpm.io/cli/run#enable-pre-post-scripts).

If you decline setting this option, but still need your prebuild step to work across `npm` and `pnpm`, then you can do something gross like this:

### Why automation matters

By automating the process of generating our data-driven pages as part of the build process, we can ensure that the latest content is always available to our users. This is especially important if our data is changing frequently, or if we're adding new tools or categories on a regular basis.

With this approach, we don't have to remember to run the script manually before each build - it happens automatically as part of the standard build process. This saves time and reduces the risk of forgetting to update the content before deploying a new version of the site.

Additionally, by generating the page content at build time rather than at runtime, we can improve the performance of our site by serving static HTML instead of dynamically generating the page on each request. This can be especially important for larger or more complex sites where performance is a key concern.

## Key Takeaways

While the full script is quite long and complex, breaking it down into logical sections helps us focus on the key takeaways:

1. Generating data-driven pages with Next.js allows us to create rich, informative content that is easy to update and maintain over time. 1. By separating the data (in this case, the categories and tools) from the presentation logic, we can create a flexible and reusable system for generating pages based on that data. 1. Using a script to generate the page content allows us to focus on the high-level structure and layout of the page, while still providing the ability to customize and tweak individual sections as needed. 1. By automating the process of generating and saving the page content, we can save time and reduce the risk of errors or inconsistencies.

While the initial setup and scripting can be complex, the benefits in terms of time savings, consistency, and maintainability are well worth the effort.

programmer

This is a brief story about the best programmer I've ever worked with.

A few years ago I worked with a celebrity developer who wrote the book on an entire category of technical pursuit.

This was pre-ChatGPT, and this rare human could produce entire applications overnight even in languages they didn't know well that worked perfectly and appeared idiomatically written.

In a one on one I asked if this person ever got stuck or frustrated despite their abnormal skill level and many decades of experience.

Their response?

"All the time".

trained

query

rag

model

rag

training

---

You can ask your RAG pipeline, "What line is the bug on? ", and it will tell you the answer almost instantly. How?

Embeddings models are the secret sauce that makes RAG work so well. How are they trained in this "asking questions of documents" use case?

In this blog post we'll unpack how embeddings models like OpenAI's `text-embedding-3-large` are trained to support this document retrieval and chat use case.

## Table of contents

## Training Data and Labels In the context of training an embedding model for RAG, the training data usually consists of pairs of queries and documents.

The labels aren't traditional categorical labels; instead, they are used to create pairs of similar (positive) and dissimilar (negative) document embeddings.

**Positive pairs

**: Queries and their relevant documents.

**Negative pairs

**: Queries and irrelevant documents (often sampled randomly or using hard negatives).

Here's what a simple pre-training example might look like in Python:

`

## Model Architecture Many modern embedding models are based on transformer architectures, such as BERT, RoBERTa, or specialized models like Sentence-BERT (SBERT). These models typically output token-level embeddings.

**Token

level embeddings

**: Each token (word or subword) in the input sequence gets its own embedding vector. I built [a demo showing what word and subword tokens look like here](/demos/tokenize).

**Pooling mechanism

**: Embeddings for each token are useful, but how do we roll those up into something more meaningful? To get a single vector representation of the

entire document or query

, a

pooling mechanism

is applied to the token

level embeddings.

### Pooling Mechanism Pooling mechanisms are used to get an embedding that represents an entire document or query. How can we condense the token-level embeddings into a single vector? There are several common approaches:

#### Mean Pooling Mean pooling involves averaging the embeddings of all tokens in the sequence. This method takes the mean of each dimension across all token embeddings, resulting in a single embedding vector that represents the average contextual information of the entire input.

This approach provides a smooth and balanced representation by considering all tokens equally. For example:

`

#### [CLS] Token Embedding In models like BERT, a special [CLS] token is added at the beginning of the input sequence. The embedding of this [CLS] token, produced by the final layer of the model, is often used as a representation of the entire sequence. The [CLS] token is designed to capture the aggregated information of the entire input sequence.

This approach provides a strong, contextually rich representation due to its position and function.

`

#### Max Pooling Max pooling selects the maximum value from each dimension across all token embeddings. This method highlights the most significant features in each dimension, providing a single vector representation that emphasizes the most prominent aspects of the input.

This method captures the most salient features, and can be useful in scenarios where the most significant feature in each dimension is important.

`

In summary:

**Mean Pooling

**: Averages all token embeddings to get a balanced representation.

**[CLS] Token Embedding

**: Uses the embedding of the [CLS] token, which is designed to capture the overall context of the sequence.

**Max Pooling

**: Selects the maximum value from each dimension to emphasize the most significant features.

These pooling mechanisms transform the token-level embeddings into a single vector that represents the entire input sequence, making it suitable for downstream tasks such as similarity comparisons and document retrieval.

## Loss Functions The training objective is to learn embeddings such that queries are close to their relevant documents in the vector space and far from irrelevant documents.

Common loss functions include:

**Contrastive loss

**: Measures the distance between positive pairs and minimizes it, while maximizing the distance between negative pairs. See also Geoffrey Hinton's paper on [Contrastive Divergence](http://www.cs.toronto.edu/~hinton/absps/nccd.pdf).

**Triplet loss

**: Involves a triplet of (query, positive document, negative document) and aims to ensure that the query is closer to the positive document than to the negative document by a certain margin. This [paper on FaceNet](https://arxiv.org/abs/1503.03832) describes using triplets, and [this repository](https://github.com/davidsandberg/facenet) has code samples.

**Cosine similarity loss

**: Maximizes the cosine similarity between the embeddings of positive pairs and minimizes it for negative pairs.

## Training Procedure

The training process involves feeding pairs of queries and documents through the model, obtaining their embeddings, and then computing the loss based on the similarity or dissimilarity of these embeddings.

**Input pairs

**: Query and document pairs are fed into the model.

**Embedding generation

**: The model generates embeddings for the query and document.

**Loss computation

**: The embeddings are used to compute the loss (e.g., contrastive loss, triplet loss).

**Backpropagation

**: The loss is backpropagated to update the model weights.

## Embedding Extraction  After training, the model is often truncated to use only the layers up to the point where the desired embeddings are produced.

For instance:

**Final layer embeddings

**: In many cases, the embeddings from the final layer of the model are used.

**Intermediate layer embeddings

**: Sometimes, embeddings from an intermediate layer are used if they are found to be more useful for the specific task.

## Let's consider a real example

Sentence-BERT (SBERT) is a good example of a model specifically designed for producing sentence-level embeddings.

**Model architecture

**: Based on BERT, but with a pooling layer added to produce a fixed

size vector for each input sentence.

**Training data

**: Uses pairs of sentences with a label indicating if they are similar or not.

**Training objective

**: Uses a Siamese network structure and contrastive loss to ensure that similar sentences have embeddings close to each other and dissimilar sentences have embeddings far apart.

## Summary Training an embedding model for Retrieval Augmented Generation use cases requires a few key components:

**Training data

**: Pairs of queries and documents (positive and negative).

**Model output

**: Typically token

level embeddings pooled to create sentence/document

level embeddings.

**Loss functions

**: Contrastive loss, triplet loss, cosine similarity loss.

**Training

**: Involves generating embeddings, computing loss, and updating model weights.

**Embedding extraction

**: Uses final or intermediate layer embeddings after training.

databases

## Table of contents

## vector database comparison: Weaviate vs FAISS

This page contains a detailed comparison of the Weaviate and FAISS vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Weaviate | FAISS | | ---------| -------------| -------------| | Local Deployment | ✅ | ✅ | | Cloud Deployment | ✅ | ❌ | | On - Premises Deployment | ✅ | ✅ |

## Scalability | Feature | Weaviate | FAISS | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ❌ | | Vertical Scaling | ✅ | ✅ | | Distributed Architecture | ✅ | ❌ |

## Data Management | Feature | Weaviate | FAISS | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ✅ | ❌ |

## Security | Feature | Weaviate | FAISS | | ---------| -------------| -------------| | Authentication | ✅ | ❌ | | Data Encryption | ✅ | ❌ | | Access Control | ✅ | ❌ |

## Vector Similarity Search | Feature | Weaviate | FAISS | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean, Jaccard | Euclidean, Inner Product | | ANN Algorithms | HNSW, Beam Search | IVF, HNSW, Flat | | Filtering | ✅ | ❌ | | Post-Processing | ✅ | ❌ |

## Integration and API | Feature | Weaviate | FAISS | |---------|-------------|-------------| | Language SDKs | Python, Go, JavaScript |  | | REST API | ✅ | ❌ | | GraphQL API | ✅ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Weaviate | FAISS | |---------|-------------|-------------| | Open-Source | ✅ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Weaviate | FAISS | |---------|-------------|-------------| | Free Tier | ❌ | ✅ | | Pay-as-you-go | ❌ | ❌ | | Enterprise Plans | ✅ | ❌ |

## A powerful pattern for CLI's that orchestrate complex deployments or workflows

Check out the project code on GitHub

In this post, I'm going to demonstrate a pattern that I've employed successfully at work in a few tools that handle complex, multi-step deployments.

**

## The goal

The goal is to combine the snappy responsive UI that Bubbletea provides with a state machine that is: * robust to failure, allowing the user to fix an issue and re-run their same command * idempotent, where possible. If a complex step completed in run #1, but the user has to re-run their tool minutes later, we should be able to keep whatever work that was completed correctly from the last run. * capable of processing even long-running tasks in sequence * capable of providing extremely detailed error information to the user in the event of an error

## Let's dive into the code *** ### Why is this pattern so powerful?

Let's dive into the code for this pattern and consider a `stage`. which is a discrete unit of work to be performed, such as running a bash script or performing a `terraform apply`.

The main idea is that every stage has an action, which represents the unit of work the stage will complete. This work could include:

running a command to ensure the user has a certain binary installed on their system

performing a `docker build` with a bunch of arguments to select the correct image and destination

reading a value from some database

running a script or other utility and waiting for its output

Each stage therefore also has an Error. If the Error is ever not `nil`, then we effectively shut down the entire pipeline. This is also a design requirement, because when we're orchestrating complex deployments, we can't successfully run the `terraform apply` at the end if our `docker build` commands for the required images never completed successfully.

The `isComplete` field and the `IsCompleteFunc` work in concert to skip any work that has already been completed successfully (likely in a previous run) which makes the pipeline speedier.

A good example of where this can save a lot of time is a `terraform apply` of some ECR repositories to hold docker images. If the ECR repositories were successfully created on the previous run, then it would be wasteful to destroy and re-apply them from the perspective of the user's time.

Instead, `isCompleteFunc` can run for this stage, and use the AWS SDK, for example, to check if the ECR repositories already exist. If they do, the function can return `true`, indicating that the work the stage was concerned about is already complete. We can just leave those repos alone, move on to the next step, and use them again later.

Finally each stage also has the concept of a `Reset` function which returns an error. This function could optionally reset a stage back to a known good state if necessary. You could imagine writing code here to delete a test value from the database, or clean up a generated file in preparation for the next run, or even run `terraform destroy` in cases where it's preferable to fail cleanly than to litter an AWS account with half-applied configuration.

By combining these fields together with a certain Bubbletea pattern, we achieve a fast, snappy UI that is extremely responsive to the user, in front of a powerful state machine that can smoothly orchestrate even complex processes with multiple different actors, tools, binaries and arguments - with zero UI lag or stuttering.

The tool can re-use artifacts that were successfully created in previous runs, and can perform teardowns when a show-stopping error is encountered, all while presenting the user with a beautiful, interactive and animated interface. Not bad for a command line tool.

### Understanding the Bubbletea lifecycle events

Bubbletea is a library for building Terminal User Interface (TUI) applications. Bubbletea publishes a great primer on the elm architecture that inspired it on their project's GitHub page. In essence, think of a Bubbletea program as similar to a game loop. When you build a Bubbletea application, your model must implement the following methods, *which you do not call or manage directly*. Instead, you allow Bubbletea to handle the event loop and you write code in an asynchronous way by passing messages to start and stop long-running or expensive work on a separate loop, essentially.

To be honest, it took me a couple of tries with some example bubbletea programs to really be able to make forward progress and not get stuck on some obscure issue. This was all due to the model not really clicking for me - but I read the bubbletea tutorials a couple of times and kept at it and eventually it started to feel natural to extend and manage a program via distinct message types and handlers in the update function - and even pleasant because it did help me to keep the program logic clear and maintainable even as it grew in complexity and functionality.

`Init`

useful for performing initial setup work or to kick off the main process

`Update`

called by Bubbletea

used for handling I/O in an asynchronous manner, which keeps the UI loop snappy and responsive.

`View`

called by Bubbletea

it renders your model depending on your model's state, which you should update via the Update function

In addition to these core bubbletea events, the state machine pattern requires a couple of other key elements I'll call out and touch on separately.

### The runStage function

`runStage` is the meat of the state machine logic, which can really be this simple. If the stage is not determined to be complete already, then run the stage's action and set its error field to the result. Finally, return the `stageCompleteMsg`, which is picked up by our model's `Update` function in order to signal that we're ready to advance the state machine to the next stage.

### The stageComplete handler

Since this is a bubbletea program, we add a new case within our switch statement in our model's `Update` function in order to handle the `stageCompleteMsg` we just returned:

### Example stages

What does a stage look like in practice, anyway? Here's a couple steps that orchestrate a 3 step pipeline, complete with all the functionality described above. You could imagine your actions being a function wherein you can write any custom or business logic you want. As long as it returns no error, the state machine will proceed to the next step

## Build ambitious command line tools!

With these building blocks, you can create robust tools that can recovery gracefully from errors, report detailed issue breakdowns to end users, and successfully drive even long-running multi-step deployments end to end.

Be sure to check out the sample code on GitHub. Thank you for reading!

For the past 3 years, I've been working at [Gruntwork](https://gruntwork.io), a DevOps startup that accelerates  your launch into AWS by giving you best-practice architectures defined in Terraform, and standing them up in your accounts in about a day.

In those three years, I have been promoted three times.

More importantly, my skill and confidence in writing Golang has improved significantly, and as a result I've experienced:

1. more opportunities to make more satisfying and impactful contributions at work 2. deeper job satisfaction (likely as a result of #1) 3. the ability to make more interesting open-source contributions across work and personal projects 4. increased capacity to help out my teammates when pair coding, to propose alternative approaches 5. massively increased confidence and speed in reviewing community and teammates' pull requests 6. massively increased confidence and speed in developing solutions that are more efficient, simple, reliable and easier to change than the ones I used to develop

In this post, I reflect on the techniques and habits that have delivered the highest return on my investments of time and effort: the winning strategies I plan to continue employing in the future because they make me more effective and content at work.

## TLDR

I hired a professional coach

I got certified

again and again

I forced myself into cross

sensory practice

I stoked my curiosity to propel me forward

I read like crazy

I constantly raised my hand to hop on the pain train

I left documentation behind

with an attitude

I maintained a second brain

I maintained a personal portfolio

I made a bunch of open

source contributions

I had hard conversations

I did code kata

I kept tabs on general language developments

I continued to evolve my development setup

## I hired a professional coach

When I started at Gruntwork, I had three major avenues for getting feedback on my work that I could use to improve:

1. Structured performance reviews 2. Comments colleagues left on my pull requests while reviewing my code 3. Direct queries I'd eventually come to make of colleagues that I trusted while pair-coding with them (What should I do to improve? What tools / methodologies / books do you think I should look into?)

This feedback was helpful, but infrequent.

I wanted to go deeper into Golang development and to receive feedback more frequently. I had found a language I loved working with and that I wanted to master. I wanted to carve out dedicated space for more effective practice.

To this end, I hired a coach. At Gruntwork we have excellent benefits, my favorite probably being the wellness / learning budget, which is a (very) generous monthly stipend you can use for books, coaching, conferences or anything health and family related.

I researched my options online and ultimately settled on hiring [Bitfield Institute](https://bitfieldconsulting.com/golang/bit). In so doing, you get to work with [John Arundel](https://www.linkedin.com/in/bitfieldconsulting/), who is a highly experienced developer and successful author of several books on Golang development, testing and tooling.

John also happens to be a delight to work with. You start out by reading a few of his books and working through the examples and exercises. Along the way you'll also get assigned some Golang projects to start working on.

Here's the first project I worked on after starting with Bitfield. [Procrastiproxy](https://github.com/zackproser/procrastiproxy) is a simple proxy you can configure to reject requests to specific domains within any window of hours you wish. You could drop it somewhere in your network and have it reject your attempts to access reddit or twitter or your favorite news site between 9am and 5pm, for example.

As you develop these projects on the side, John will review your work and provide feedback, including detailed code reviews. This is where I began to unlock massive value from working with John. These initial code reviews were unlike the drive-by `LGTM` that we've all left and received, or cursory glances and some random questions to feign engagement in your pull request.

These were serious, deep code reviews. I questioned the entire project setup by the time I was done reading John's comments. "Why did I think that adding more was better, especially when it came to configuration of my services? ",  I asked myself.

John taught me to value simplicity and to look to the standard library and consider cleaner solutions from the outset. To think more deeply about the interactions that others will have with my software as users and maintainers.

To think deeply about whether or not something was truly clear from its name, and whether or not the code generally "looked happy", which is a separate consideration from whether or not your program is correct.

Working through these code reviews, reading John's books and building these projects under John's tutelage has tremendousy improved my capabilities as a developer. As a result, I find more satisfaction and enjoyment in my work.

It's my favorite part of the week or month when I get to pair with John, so having a coach also helps me to maintain a virtuous cycle of enjoying learning more about what I do, which causes me to do what I do slightly better, which ultimately makes work more enjoyable.

The other thing about working with John that was tremendously helpful for me was the work therapy: hearing that someone more experienced and skilled than you are encounters and has to contend with the same annoyances, difficulties, technical warts and sharp edges, organizational headaches, etc is validating and encouraging.

It's the human, emotional aspect of mentorship, being mirrored as a developer, that helps you to feel that you're generally on the right track.

## I got certified

again and again

In my experience, cloud certifications tend to divide folks. Proponents of certifications will emphasize the learning: studying for certs can help you to fill in the gaps in your knowledge such that you can make more effective use of the technologies you're being certified for.

Detractors tend to point out that a finger pointing at the moon is not the moon; that there's an entire cottage industry assisting folks who do not have significant development backgrounds in cramming to pass certifications, even though they lack the foundational skills necessary to put their certification knowledge to good use by delivering working software in a professional setting.

It's entirely possible to be certified in a bunch of fancy technologies that you have absolutely no clue how to use. Doesn't this negate the entire value proposition of certification programs in the first place?

In my case, I reasoned that I wanted to build up a significant grouping of relevant and generally well-respected certfications for two main reasons:

1. I was planning on building up all the knowledge being tested for by the certifications anyway. I'm speaking about Terraform and AWS certifications here. 2. In any future competitive employment situation, and in future business negotiations, I want the extra chips to bargain with. If you're already sold on the idea that I'm generally ompetent and not a bullshitter, then my having the certifications is a reasonably solid signal that I'll be effective when working with the technologies I'm certified for

In any event, in my personal experience, preparing for these certfication exams has provided the real value for me, and I have benefited from using other people's courses and materials. So far, [Adrian Cantrill](https://cantrill.io/) provides the highest quality [AWS training](https://learn.cantrill.io/) I have come across.

In my case, I picked up the following AWS certifications:

[Cloud Practitioner](https://www.credly.com/badges/df691558

e926

4d11

93ca

c15e1707c954/public_url)

[Solutions Architect Associate](https://www.credly.com/badges/baf2a178

fbb6

4e3b

bee3

a4e51bf1ecb4/public_url)

[Developer Associate](https://www.credly.com/badges/6b6bcd25

c114

40ec

988c

d85118a62413/public_url)

[SysOps Administrator Associate](https://www.credly.com/badges/a8867c79

5eef

4231

a939

6e4e2993ca4b/public_url)

I found that my confidence in working with AWS services skyrocketed after completing the Solutions Architect Associate, but your mileage may vary.

## I forced myself into cross-sensory practice

When I reflect on getting certified, I find that the real value for me was having structured time set aside to play around with the various AWS services to better understand them, and to leverage what I'm currently calling layered or cross-sensory learning:

At a high level, I find myself roughly following this pattern with my own learning:

The primary source in this case could be a textbook or book on Go that I'm excited about, such as [The Power of Go Tools](https://bitfieldconsulting.com/books/tools) or [Concurrency in Go](https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/). Or it could be a YouTube video or a tutorial on somebody's blog. It could be the powerpoint slides / videos of one of Adrian's Cantrill's AWS certification courses.

I'll read the primary source once or twice initially, get a general sense of things, try to build a mental model I can use to ask and answer questions about the subject matter.

Next, I'll look for opportunities and barring any, invent some, to use my new fledgling and abstract knowledge in an implementation: either in a side project, or an open-source project I contribute to, or the next work task, if appropriate.

If I've been reading up on Go channels and different usage patterns, for example, I'll create a local repo and create a bunch of different examples and experiments in different folders.

Once I get that working, I'll tweak it in a couple of ways to play around with channels, the order of operations and channel closes, contexts, cancellations, etc.

By the time I've gotten this far in implementing or attempting to implement something, I've encountered:

at least several UX snags or ambiguities that make usage of this thing or language painful. These are useful data points

A couple of questions about behaviors that I'll want to further explore or understand more completely

bugs, possibly or limitations in documentation

my own ignorance

repeatedly

These are all inputs that can be split out into separate notes and followed up on separately, but the key point is that they propel the overall inquiry and learning forward by opening up new pathways for additional connections.

The more I studied for certifications, the more I came to realize how totally ineffective it was to simply try watching videos whenever I had some free time. It was just not a meaningful return on investment.

Reading the code snippet in the textbook is a start. A very shaky start.

Typing it out into your machine is better. Modifying the resulting program after you type it out is even better. Tweaking it and re-running it until you understand it is better still. Ideally, once you've achieved this, you can continue modifying the proof of concept to explore adjacent ideas or solidify other concepts you recently learned.

Watching the AWS certification course powerpoint slides on how S3 works is start, but finding the Go SDK for S3 next and writing up a simple program to put items in your bucket is better.

Extending this program to flex all the various S3 API calls you just learned about is better still. Writing up a blog post explaining everything you did and learned along the way, and using that post to teach a friend or colleague next is better still.

Buying the course is not the same as having already invested all the sweat equity (effort and likely, frustration) required to actually understand the key concepts the course teaches. Plan accordingly in terms of time.

Buying the textbook is not the same as having worked through the textbook in order to understand the examples, including all the bugs, issues, edge cases, missing binaries, operating system incompatibilities, etc, etc that will necessarily stand between you and having the end result you desire.

You have to flex the material to really understand it. You have to get kinesthetic with it - actually go through the pain to get the dependencies installed and the runtime on your machine. Actually get your code implementing your weird angle or idea that you're trying to validate to compile. All of the extra things you'll have to figure out along the way are very relevant to your daily work, too.

## An example output of this cross-sensory learning

As one real example of an artifact that came out of this process of iterative and layered exploration, here's [the open-source CI/CD pipeline I built](https://github.com/zackproser/aws-react-golang-sam-codepipeline) using AWS CodePipeline and AWS serverless application model (SAM) and defined in CloudFormation.

After I published the code, I wrote up [a tutorial walking through its usage here](https://zackproser.substack.com/p/how-to-build-a-reactjs-and-lambda).

Why CloudFormation? Because I use Terraform all day and wanted to understand the experience of working with one of its alternatives.

## I stoked my curiosity to propel me forward

In a previous section, I say that I "forced myself into cross-sensory practice". That's a bit heavy handed and not totally accurate. I've also found that it's critical to maintain your motivation by allowing your curiosity to drive you forward to further discovery and learning.

It's ideal if you are excited to try out that slick looking new Go library to see if it can solve some of your problems in a more elegant way. Always keep a running list of projects, tools and experiments you are excited to try out.

Recently, for me this role has been nicely filled by everything that [the Charm organization](https://charm.sh/) is putting out on GitHub. In short, their popular and beautiful projects Bubbletea, Glamour, Lipgloss and others are making developing ambitious, gorgeous and responsive applications for the terminal a delight.

That didn't mean it was necessarily easy - I still had to wrap my head around how Bubbletea programs are structured and how they work.

I wrote a couple of internal proof of concepts (POCs or POS, depending on whether or not you can see the code) using Bubbletea, one that contemplated offering a piece of our sign-up flow via an SSH connection using Charm's Wish project to serve Bubbletea applications over SSH.

In my free time, I also created:

1. [Teatutor](https://github.com/zackproser/teatutor), an app for creating quizzes that people can take over SSH. It's not finished - it still needs to be genercized so that it's easy for users to launch and maintain their own quizzes, but it was a good excuse to practice using Bubbletea more.

2. [unicode-cli](https://github.com/zackproser/unicode-cli), a completely unremarkable practice repo. The point is that it was an experiment I used to solidify some piece of my mental model around how Bubbletea programs worked.

## I read like crazy

I keep an external list for organization and motivation purposes. I happen to use Goodreads for this. It's nice to be able to wing your friend your recent reading history with a single link when you're discussing books or trying to figure out what to read next.

I read about work. I read about working. I read about organization, note taking and learning. I read about difficult conversations, the common dysfunctions of teams and how to improve things at work.

I read about [the War of Art](https://stevenpressfield.com/books/the-war-of-art/).

I find that I get a handful of helpful different ideas from each book that stick with me.

These ideas have in some cases helped me to dissolve mental blocks that I dragged around for years. Keeping up this habit, I expect, will therefore continue to pay dividends.

Reading also compounds over time, and you can feel it every time you encounter an idea you already understand, or a piece of knowledge that you already acquired from a recent reading of a different source, or additional details to hang on the scaffold of a concept you already acquired.

Nowadays whenever anyone I'm close to or work with recommends or mentions a book, I pick it up immediately and queue it for future reading.

Here are the books that I read over the past 3 years that I credit with having the most positive impact on my life:

[The Pragmatic Programmer: from Journeyman to Master](https://www.goodreads.com/book/show/4099.The_Pragmatic_Programmer?ac=1&from_search=true&qid=vxMyprIOmb&rank=1)

[Code For Your Life](https://bitfieldconsulting.com/books/code)

[Soul Without Shame](https://www.goodreads.com/book/show/98402.Soul_Without_Shame)

[The Phoenix Project: A Novel about IT, DevOps, and Helping Your Business Win](https://www.goodreads.com/book/show/17255186

the

phoenix

project)

[The Five Dysfunctions of a Team](https://www.goodreads.com/book/show/21343.The_Five_Dysfunctions_of_a_Team)

[Thinking in Systems: A primer](https://www.goodreads.com/book/show/3828902

thinking

in

systems)

[Getting Things Done: The Art of Stress

free Productivity](https://www.goodreads.com/book/show/1633.Getting_Things_Done)

[The War of Art](https://www.goodreads.com/book/show/1319.The_War_of_Art)

[Grokking Simplicity: Taming complex software with functional thinking](https://www.goodreads.com/book/show/52257623

grokking

simplicity)

[How to Take Smart Notes: One Simple Technique to Boost Writing, Learning and Thinking](https://www.goodreads.com/book/show/34507927

how

to

take

smart

notes)

## I constantly raised my hand to hop on the pain train

In order to grow we need to be stretched, and one of the worst things a creative person can do is become comfortable.

The more patterns you expose yourself to, the more you'll find places for them in the software you're building. Meanwhile, confidence is borne of demonstrated ability, so the more complex software tasks / fixes / enhancements / refactors / designs you handle - the better you'll be able to handle those in the future. The more difficult deployments you do, the more calloused you become to difficult deployments (for better and for worse).

At work, this meant that I always raised my hand to take on deployments even though the process was difficult, ill-defined, complex, tedious to repair and demanding both general development knowledge and deep specialty knowledge of several topics and technologies.

Before we had matured and grown the process and team, deployments could stretch out your days and evenings and demoralize you with a seemingly endless stream of blockers, obstacles, random blips, AWS changing behaviors and quotas out from under you without documenting or announcing them, weird edge cases and strange customization requests that complicated everything further.

This is speaking just to the technical side of things, before getting into the at least equally complex side of managing all of the humans involved on both sides (or three sides, if we're working with a partner) and all their competing emotions and needs.

It was hard and frustrating. It was reliably stressful to work on, to pick up responsibility and to answer for a given deployment, to see it through start to finish and to be the person looked to internally and externally to pull it off each time. And the longer I did it, the stronger I got. As a previous manager observed, "struggle brings strength".

When one deployment was finally closed out, as arduous as that one had been, once I had finished writing up the retrospective and filed all the issues to close it out, I would always raise my hand for the next one that became available, even if it fell on the next day.

My only regret is that I didn't do even more deployments.

## I left documentation behind - with an attitude

The attitude being: "I don't want to have to explain this ever again - not even to one more person."

Because, inevitably, your co-workers or your customer will need to know where that new secret you uploaded is, how the new feature processes records and where the updated endpoint lives - and they will need to know it exactly when you're in the middle of the next interview or 1 on 1 or important strategy meeting.

For this reason, I want my doc to be so thorough, clear, replete with helpful information, organized with a table of contents and free of mispellings that I can just return a single link to my requestor and feel confident that I've given them everything they need to unblock themselves.

When I think selfishly in this way - that the documentation is actually for me - it greatly increases my motivation to leave clear and helpful artifacts behind.

I know that the probability is high that *I could be* the next person who needs that information, about 2 years from now, after we've stopped using the tech stack in question as a company. So, I try to be kind to my future self.

## I maintained a second brain

I use [Obsidian](https://obsidian.md/). and roughly follow the philosophies outlined by:

1. [Getting things done](https://www.amazon.com/Getting-Things-Done-Stress-Free-Productivity) 2. [How to Take Smart Notes](https://www.amazon.com/How-Take-Smart-Notes-Technique-dp-3982438802/dp/3982438802/ref=dp_ob_title_bk)

How to Take Smart Notes is a real gem. Even though I had encountered many of the ideas iit set forth before, this book was the first for me that truly demonstrated the many benefits of the Zettelkasten system. I read this book with increasing euphoria and excitement.

I feel that I've only begun to scratch the surface of the eventual productivity and learning enhancements I'm going to unlock by continuing to maintain and consult with a second into the future.

I use Obsidian Sync to keep my notes synced across my phone, laptop and desktop (Android + Linux) it works flawlessly, allowing me to write out blog posts directly into obsidian on my phone so that I can polish them a bit when I'm on my laptop next and publish them directly to the web in vanilla markdown.

This subject is meaty enough to deserve it's own future deep dive post. For now, I'll say that having a central place for all my notes, for every "open loop" in my life, and having a low friction, fun methodology for continuously iterating on ideas, notes and posts until they're ready to publish is a boon for me.

## I maintained a personal portfolio

I keep my personal portfolio at [https://zackproser.com](https://zackproser).

I'll expand on my thoughts around maintaining a portfolio in a different post, but for now I'll constrain myself to the highlights of why I believe doing this is worth the effort:

It's a good excuse to remain plugged in to another tech stack. I have done a good bit of frontend development in the past, but for the past few years my focus has been on IaC, systems automation, CLIs, Go, Bash, Python, AWS + cloud, etc. All the same, I like to remain at least somewhat plugged into the frontend world. I like to be able to build my own UIs when needed - and knowing the general ecosystem, its popular projects and their upsides and pain points makes me more well rounded and a better complement to any team members who *are* primarily working on the frontend day to day.

It serves as a single place on the internet that you control which you can use to show off your past work and your skill set. Run it on your own domain so the bastards can't hold you down. Never gone through maintaining your own domain before? Dealing with DNS? Adding your own subdomains over time and configuring HTTPS? All the more reason to run your own site, to gain this experience.

It's a good excuse to learn (and care) about common web performance issues, such as asset bundling and fingerprinting, good CI/CD practices for quick iteration and resolution, preview environments, caching, geo-aware customer routing, analytics, etc, etc, etc. As ever, some of the things that end up being true competitive edges for me at work are things I figured out screwing around with one of my many side projects. It doesn't really matter if your site is extremely popular or not, building experience with these common issues, common workarounds, solutions and tech stacks is - at the end of the day, just that: experience. It makes you more effective, marketable and expensive

It can be a good way to cross-pollinate with and to fuel or enhance your blogging efforts. It's not for everyone, and there are plenty of good arguments for why you should just use a simple provider like Squarespace to build and maintain a separate blog, but, speaking for myself, having done all the brain damage to get posts working the way I wanted in my site increased my motivation to post my own content on my site, as well as my own satisfaction in seeing my content live on a site and system I built

## I made a bunch of open-source contributions

Handily enough, at Gruntwork we have a lot of Golang repositories that we maintain: command line interfaces (CLIs), and various bespoke tools for automating Kubernetes needs, for example.

I had plenty of options to choose from, and mostly concentrated on [cloud-nuke](https://github.com/gruntwork-io/cloud-nuke) last year, our open-source tool for cleaning up AWS accounts quickly and easily by finding and deleting resources for you.

But I was also able to work on additional improvements for [git-xargs](https://github.com/gruntwork-io/git-xargs) and to add some login helper commands to our [gruntwork](https://github.com/gruntwork-io/gruntwork) command line tool (which happened to leverage my recently acquired Bubbletea knowledge).

I had spent enough time with cloud-nuke to know that it could benefit from UI enhancements that made it easier to read the logged output of every operation it took. I was able to use [pterm.sh](https://pterm.sh/) to make the output much nicer to read. Doing this also involved adding a new subsystem that was capable of tracking every individual operation in AWS and presenting the final results in a nice table format.

Prior to my changes, cloud-nuke output a flat list of log lines to `STDOUT` that may or may not include the information you need to actually understand what happened to which resource.

Here's a video demo showingthe UI looks after my enhancements:

[! [asciicast](https://asciinema.org/a/525446.svg)](https://asciinema.org/a/525446)

I mention this because working on open-source can be excellent for many reasons:

1. It's a way to give back to the community - it's a small form of thanks for everyone who ever took the time and effort to publish a tutorial or stack overflow answer that taught or helped me when I was starting out 2. open-source development can improve your resume and raise your professional profile. Significant open-source contributions or a history of maintaining popular open-source repositories are strong hiring signals for many organizations 3. You can find real-world problems and software to work on - which can be more effective than following along in text books where you don't have a lot of domain knowledge yet

## I had hard conversations

Left to their own devices, other people will fuck up your career progression at every single opportunity they get. In some cases it's because they're malicious or jealous - but this applies even to the excellent managers, owners and founders who care about you and have your back.

The key is *left to their own devices*. Even those looking out for you, wanting you to succeed and trying their best to support you *cannot read your mind*.

If you haven't already, recently and repeatedly been having conversations with ALL the relevant stakeholders about where you are in your progression, where you want to go and what changes, improvements, new skills will be required to get you there, then that promotion is not happening.

Even though you did all the things and some extra.

Even though you also built those handy utilities tools that one team is now using.

Even though you taught Sparky how to refactor their JavaScript to use ES6 every Friday afternoon.

If you haven't had several discussions with your manager and possibly their managers too, building a clear, shared understanding of the impact you're making and where you want to go career wise, then that promotion that's a no-brainer in your mind is not happening.

Worse still, if you miss the boat this review cycle, it will still require a couple rounds of conversations next time around to align you and your management and any other stakeholders that have influence into whether or not you get promoted. This means that, even if you don't expect or want a promotion for about a  year or more out from today, you still need to start discussing it today.

Nobody is going to care as much about your career progression as you are. This means it's your responsibility to:

1. Figure out where you want to go 2. Regularly request feedback about the delta between your current and your desired roles 3. Regularly do the hard work, put in the sweat equity, to develop and demonstrate your new skills and capabilities successfully, to actually deliver real and meaningful business value (not simply an artifact you can point to that uses your new skill set but does not move the needle for the business. This is commonly conflated with impact - but they are different things) 4. Regularly document succinctly and clearly what you did, so that your impact does not slip under the radar of the committtee / person making the promotion decisions 5. Regularly ensure the key stakeholders see / understand your documented improvements and contributions

Of course, like everyone else I learned all this the hard way.

So now I can say that ever since I began having difficult conversations about compensation, work roles and work load, my career progression has greatly improved, and I found alignment with my workplace's needs and my own, which also reduced my overall frustration day to day.

## I did code kata

For extra practice, I used [exercism.io](https://exercism.io) and others like [codecrafters.io](codecrafters.io). These are great supplemental exercises because they'll help build your muscle memory for language syntax, common algorithms and patterns.

I tend to create an open-source repository where I'll iterate on my solutions - for my own edification and in case it's helpful to anyone else in the future:

[Exercism](https://github.com/zackproser/exercism)

[Algo practice](https://github.com/zackproser/algo

practice)

## I kept tabs on general language developments

Just need a decent mailing list for this - I like [Golang Weekly](https://golangweekly.com/)

## I continued to evolve my development setup

In my previous iteration, I was using vanilla vim with vim-go. That was a large improvement over my JetBrains / GoLang IDE setups.

More recently I started experimenting with Neovim and Astronvim. Good excuses to learn more about vim, neovim and lua.

Seeing the differences between Vimscript and the Lua approach is interesting. Having everything work "out of the box" in an IDE-like experience but in neovim is interesting.

Learning how to tweak my configuration so that `go build` is run on the file everytime I hit `backslash + b` is both fun and helpful to my workflow.

My setup and configuration is currently in flux. I continue to experiment with various tweaks and enhancements as I learn more.

## Thanks for reading

Those are the highlights. I hope this post has been helpful or interesting to you in some way.

of

software

of

software

convo

---

I've spoken with many investors and analysts over the past year and a half about AI assisted developer tooling. With some exceptions, their questions tend to frame developer tooling as a horse race. They want to know if Codeium has any chance of beating GitHub's Copilot, given GitHhub's resources and superior distribution story (how many millions of developers can they email whenever they want? ).

As someone who codes and builds systems every day, I've had enough of these conversations to see that folks on the outside looking in are trying to understand what makes one tool better or more effective than the other.

But that's not the most important question.

The most important question is: how will developers create software in the future? How will they *want to* create software?

Today, you can already talk to your laptop to build applications and systems. What will we have tomorrow?

Specifically, I'm talking about interfaces. What is the the ideal near future interface for developers? Will they be typing in code or instructions? Speaking to an agent or group of agents?

A year and a half ago, I started experimenting with [Codeium](/blog/codeium-review), and then essentially [every other tool I could get my hands on](/devtools).

Codeium, GitHub Copilot (in its original form), and many other tools help developers code faster, by looking at what they've written and making suggestions. Autocomplete tools run where developers are coding and make predictions so that hopefully the developer need only accept the suggestion and continue typing.

And that's all well and good. And it became table stakes for developer tooling in about the first 8 months of the GenAI boom.

Cursor and Zed are two tools that are pushing the boundaries of what's possible in developer tooling *by changing the input from typing to plain English. *

By the way, Cursor has its own autocomplete built-in, although folks who tend to adopt Cursor happily end up typing in less and less code (maybe more prose).

I wrote about [my experience adopting Cursor here](/blog/cursor-review). Initially, I chafed against VSCode's UI and behavior, but once I had used Cursor for several weekend and evening hacking sessions, I simply could not unsee my own productivity gains.

In fact, using Cursor has only pushed my expectations higher - and I suspect this year I'll be able to build an app by speaking into my phone while walking my dog.

If I were just an autocomplete tool, I'd be extremely worried.

Read the article

interviewing

prior

screen

sharing

makes

me

control

your

own

web

gets

 Blogging is a very worthwhile hobby, especially if you're trying to grow your tech career. Here are some ways I've recently benefited from this blog:

In the past several years that I've been building and maintaining this blog, it has gotten me:

Jobs, including my most [recent role at Pinecone.io](/blog/i

am

joining

pinecone

io)

Extra cash on the side by way of research firms reaching out to pay me to speak to potential investors

Next.js and other recent frontend skills that slotted in perfectly in my last two roles, one as a DevOps engineer and now as a developer advocate

Hands on experience in what is practical and what is not for rapid iteration on the web

A deeper appreciation for performance and quanitifying that performance via metrics like pagespeed, time to largest contentful paint, etc

Sufficient regular traffic to my content to make me realize that continuing to blog is worth my time

Endless opportunity to sharpen my writing skills and craft

I highly recommend that everyone who is serious about their tech career run and maintain their own blog. You may be surprised at how many benefits it comes with, assuming you are willing to put in the regular effort.

## The easiest interview loop I've ever had

 This last interview loop was the easiest one I've ever had - partly due to experience and time, but partly due to my blog and the large stock of prior work I was able to show.

I normally dread interviews, despite loving what I do for a living and being excited to work at good companies. However, in large part thanks to this blog, my last round of interviews was my easiest and most pleasant ever.

I even pivoted my career from pure software engineer / full-time code slinger to a developer advocate role that was new for me - and even with this wrinkle in a tough market, I started with five companies I chose myself, continued all the way through with two, and ultimately got two great offers in hand that I was able to compare.

I've been writing for a long time, but in the last year I decided to step up my publishing game, overhauling my [old shitty and torturously assembled version](/blog/maintaining-this-site-fucking-sucks) into this latest version you're reading now that [I'm deeply in love with](/blog/maintaining-this-site-no-longer-fucking-sucks).

I also started publishing more [YouTube videos](https://youtube.com/@zackproser) while experimenting with different equipment, software and techniques to increase the quality of my finished products.

Meanwhile, I kept up my usual open-source development work, building tools and software in public, open-sourcing it and writing or recording demos of it.

The result is that by the time I landed in the interview loops for the two companies I was seriously considering, I was able to hijack each interview call by asking: "Do you mind if I share my screen real quick?" which surprisingly everyone said: "Sure, go for it!" to.

 To my surprise and delight everyone was more than happy to let me share my screen and show them my videos, blog posts, open-source projects and demos.

When I shared my screen, I had my latest and best posts, software demos, and open-source projects, replete with animations and eye-candy in addition to high quality READMEs in separate tabs, which I clicked through while talking them through my work.

I'm not exaggerating when I say that for both companies, the 5-7 folks I interviewed with all appreciated this and said, "That's exactly what we're looking for". It was easy for me to sell these folks on the idea that I could do the work because I was presenting them with hard, public, reviewable evidence of my having recently completed the same type of work.

 Having tons of prior art makes interviewing for certain roles a lot easier.

Of course I read their job descriptions carefully, but the point is that I would never have been able to pull together this much content to share if I hadn't been steadily blogging about software, development, automation, infrastructure as code and generative AI for the past year.

I had two great offers in hand after what was certainly the least stressful overall interviewing experience I've ever had in tech. Some of this is just time and experience, but a lot of it was my regular content production habit.

## Control your own destiny

This is often also referred to as the acronym POSSE, for publish on your own site and syndicate everywhere. Put your best work on your domain, that you control. Bonus points for making your domain your name like zackproser.com or a brand that you've already established.

 Control your own destiny by owning your domain.

Content farms like Medium or innumerable others that have come and gone since I started writing on the internet can and will change their rules anytime it suits them and their share-holders and they couldn't possibly care less about you and your content that you've worked so diligently over the years to build up.

Rather than propping up someone else's domain with your excellent work, own your own domain and publish everything important there. Use content farms like Medium to write a shorter, teaser version of your actual post that poses the central problem and offers a high-level look at the solutions while linking back to your full article on your domain.

As others have pointed out, there's also a reputational tax for publishing on Medium or other low quality text mills - while serious writers and companies do host their entire blogs there - the vast majority of the content on Medium is pure trash - truly liquid dogshit meant to do nothing more than capture traffic via keyword stuffing.

This means that when you hand someone a link to Medium and ask them to read your latest post, you're already off to a bad start.

## You can showcase your work and build a portfolio

 Your tech blog is the perfect place to showcase all your work for prospective employers.

For my past role and my current role, whenever I write a post at work or make a video that I'm proud of, I add it to my blog / portfolio site.

My employers don't mind an extra backlink and I'm careful to not duplicate any of the content - when it's a blog post, I just render a button that points to the original post on my employer's web property.

YouTube videos are different. I'll just embed them, but again my employer doesn't mind getting more views on their content. The point is that I get to take these with me, and they are all a piece of my overall portfolio. I've published blog posts that I'm proud of on the Cloudflare engineering blog, on Gruntwork.io's blog and on Pinecone.io's engineering blog and learning center, and I link to all of them from [my own blog](/blog/).

## An excuse to run a real web property

When you build and maintain your own blog, you get to try out different technologies and see what works for you. For myself, I knew that I wanted to run the latest next.js framework and that I wanted to host my blog on Vercel and try out Tailwind CSS and UI.

I had come to this realization by previously home-brewing the older and shittier version of my site with nuxt.js, finding that to be **almost exactly what I wanted** and having to shoe-horn in a bunch of special features and extensions to my homebrewed markdown system.

By the time I got around to overhauling my entire site, MDX had matured to the point that it included most of the things I had wanted out of the box - the ability to mostly write markdown for quick post iteration, the ability to intermingle react components where needed to use a common layout or quickly define and drop in newsletter sign-up forms or even my own CTAs and ads for my consulting services.

I wanted the ability to author code blocks of any kind directly in my post and I wanted outstanding image support with all the lazy-loading, performance optimized, responsive image goodness that Next.js bakes into its easy to use `` component.

I also knew I wanted to host my site on Vercel and that I wanted my site to be completely static once built, with serverless functions to handle things like form submissions so that I could customize my own email list tie-in and have more excuses to learn the Next.js framework and Vercel platform well.

 Running your own tech blog is a great excuse to go deep on web performance monitoring and pagespeed optimization.

Is this fun and would I do it anyway in my spare time? Yes. Did all this experience building a real website and setting up and maintaining my own email newsletter, capture forms, testimonials pages, light vs dark mode and Tailwind CSS help me in my actual day jobs? Also yes.

If you've been itching to get better at a given framework or more familiar with a new stack or technology, running your own blog is an excellent way to be your own guinea pig and see what works and what doesn't. By the time you're making an actual recommendation at work on a Zoom call, you'll be able to speak more from experience and less from you rectum, which your co-workers will also appreciate.

## Blogging can be lucrative

It's always the "throwaway" posts that you aren't sure are even worth writing down that end up outperforming everything else you've labored on. In my case, it was a quick review for my favorite AI-assisted dev tool for code completion, [Codeium, which I reviewed on my blog](/blog/codeium-review) several months back.

Codeium is a [GitHub Copilot](/blog/github-copilot-review) competitor and, wouldn't you know it, other people from developers to venture capitalists to product managers at competing companies to folks considering launching their own startup in the space want to know if it's good or not.

So here I find myself starting to get emails from research firms asking if I'll get on the phone for 30-45 minutes in exchange for several hundred dollars per call to help give these folks direct insight into the experience of using Codeium, what's working well and what isn't and [how developers such as myself](/blog/how-developers-evaluate-ai-coding-tools) tend to evaluate such tools.

Blogging begets blogging and I've found reason to share more insight on the many AI-assisted developer-centric tools and services I've experimented with and combined over the past year and the more I write about what I know firsthand, the more my overall traffic picks up.

The more my overall traffic picked up, the more I was incentivized to figure out subtle but effective CTAs such as this one:

in order to let those same folks know they can come to me directly for advice, product feedback, insight and distilled experiences anytime they like.

There are many such stories from bloggers all over the web who found unexpected opportunities just from continuously writing about what they knew well, so I am in no way an outlier here and I'm really just getting started.

As Kent Beck recently noted, you should "Publish everything", something that immediately made sense to me the moment I read it, because it's ultimately an emotional decision as to whether or not you want to share something publicly. Hint: you mostly do. Because you never know what's going to hit, and no, unfortunately, it's rarely that post you love the most that you labored over the longest.

Different folks find different value in your work than you do and that's okay. Just keep writing regularly.

## It gets easier, faster and more enjoyable the more you do it

 Blogging gets faster and more enjoyable the more you do it.

This is the case with most things in life and I happen to enjoy acquiring and honing skills, but I've also noticed the more that I write, the easier it gets to write, and the more that I find myself able to compose the rough draft of a post in my head while doing something completely different away from my computer.

That means when I get back to my laptop all I have to do is type it out. I found this to be the case to such a degree that I started another [Next.js side-project called Panthalia](https://github.com/zackproser/panthalia) which allows me to quickly:

Speak my thoughts or full sentences into the app on my phone's browser while I'm out and about or walking around

Request images for the post via StableDiffusionXL courtesy of Replicate.com's API

Instantly have the app open a pull request against this portfolio site of mine which hosts my blog

Rapidly iterate on any edits for this post and any other posts I might have in flight concurrently

I've had a great deal of fun working on Panthalia, it's useful to be and I can continue tweaking it until it's the fastest way for me to put out high-quality content, and you never know where these projects will lead.

I do know that working on it continuously for three and a half months seriously sharpened my Next.js knowledge and kept my JavaScript and TypeScript skills current, which, again fed directly into my day job where all of those skills are relevant, since I do open-source development and maintenance on Python and TypeScript projects in addition to my content production work.

I think of my blog as a laboratory that is always open for me to experiment with.

## It's a fun hobby

Now that I'm older and mostly home-bound due to family responsibilities, writing and coding are still things I can do and enjoy whenever I have the free time. I really enjoy the loop of having an initial thought or idea about a story to tell, arranging the structure of the post in my head, typing it out and refining the language and then generating pixel art images to intersperse into my posts to help tell the story and treat my readers to a little eye-candy.

If you've made it this far and you're coming from Hacker News and would like to cast aspersions on my use of AI generated pixel art, then allow me to be the first person today to invite you to go fuck yourself.

 Thanks for reading and see you next time!

I like them and it's my blog. I don't write on Medium anymore, or anywhere else where I don't make the rules. The rule is: If I like it, it ships. And I like my AI-generated pixel art quite a bit.

a

realizing

a

secret

was

pre

commit

preventing

a

secret

from

 A developer watching the API key they accidentally committed to GitHub migrating throughout the internet - thinking of the Slack messages they're about to have to send...

Committing a secret, like an API key or service token, to version control is a painful mistake, which I've done myself and seen plenty of others do, too. Fixing it usually involves at least some degree of emotional pain - you need to announce the incident and work whatever incident process your organization might have.

You may need to ask others for assistance, either in generating a new secret or helping you rotate the compromised one if you don't have sufficient access yourself.

 Engineers from the Ops, API and frontend teams attempting to excise an exposed secret from Git history, while the director of engineering sobs in the background

Depending on the size of your org, this could involve filing some tickets, starting a couple of different conversations, pulling other colleagues off what they were focusing on, etc.

**It sucks** - and it's worth investing in a process that can help you by preventing you from committing anything that looks like a secret in the first place.

## ggshield is an amazing tool from GitGuardian

You may have heard of GitGuardian - a service that runs against public GitHub repositories, scans them for secrets (API keys, tokens, passwords, etc) which may have been accidentally committed, and then emails you an alert and some helpful tips on remediating the issue.

I was delighted to discover they also have a command line interface (CLI) that anyone can use to scan their local working directory to ensure no secrets are hardcoded or otherwise exposed.

Even more powerfully, you can integrate `ggshield` with a git pre-commit hook in order to ensure that every single time you attempt to commit code from your machine, you automatically get a sanity check ensuring you're not about to leak something sensitive.

## What does it look like when ggshield saves your ass?

Here's a screenshot of a test I ran against a local repository. First, I used `openssl` to generate a certificate that was just sitting in my local repository. I then ran `ggshield secret scan repo`, and the tool correctly found my secret, before I committed it and ruined everyone's day.

## It's even better when combined with git hooks

Git hooks allow you to "hook" into different git lifecycle events in order to run your own code. For example, the `pre-commit` hook allows you to run your own code *before* you create a new git commit.

This is the perfect place to scan for secrets, and `ggshield` supports this workflow out of the box. GitGuardian has [great documentation](https://docs.gitguardian.com/ggshield-docs/integrations/git-hooks/pre-commit#global-pre-commit-hook) for all the ways you can use `ggshield` alongside git hooks, but I personally wanted to run it as a global pre-commit hook.

This means that for any and every repository I'm working with, `ggshield` will scan my local git changes for secrets. This is a nice way to automate away needing to worry about letting a random token or certificate slip through when you're in a hurry.

Of course it's critical to perform code reviews, and ask your team for a second set of eyes to ensure you're keeping your secrets out of version control, but having this extra layer of automated scanning works very well in concert with these other best practices.

 ggshield preventing a secret from escaping

Wikipedia is known to be structured in such a way that [many articles ultimately lead to Philosophy](http://www.xefer.com/2011/05/wikipedia) - if you click the first main body link of each article in succession.

Wisdom Seeker performs this task for you automatically and reports on the path it took to reach Philosophy.

It also knows when it is stuck in a loop - and will report loops as well as meandering paths that will never arrive at Philosophy. To try it out, paste a Wikipedia link - Wisdom Seeker's report includes the full links to each page, so you can follow along manually.

## Screenshots

**_Easy one-step form accepts any Wikipedia link_**

## Key Features

Visits Requested Page and Pathfinds to Philosophy Article

Is Aware of Being Stuck in a Loop

Reports Loops and Meandering Paths That Will Never Lead to Philosophy

Reports Full Path with Links So You Can Follow Manually

## Technical Details & Takeaways

Wisdom Seeker was a fun app that explored an interesting quirk of Wikipedia that I was not familiar with previously.

The biggest challenge in building this app was selecting the exact first link within the body paragraph, given that each Wikipedia page has so many links.

Ultimately, a combination of aggressive jQuery style selectors and regex filtering of retrieved HTML did the trick.

---

{metadata.description}

Read article

---

This was the first article I published while working at Pinecone:

Read article

convo

convo

convo

model

ai

assisted

The first time my colleague at Pinecone got me to try out Cursor, I hated it. VSCode always felt too heavy to me - the experience reminded me of running Windows. (Cursor is a fork of VSCode, with significant modifications).

Why am I clicking all of this garbage? What happened to all my beautiful terminal real estate? Could you throw up any MORE toast notifications when I try to load a file to edit?

But today, Cursor is my daily driver, and I'm having more fun creating more ambitous software more quickly as a result. What changed?

The reason Cursor I use cursor for professional and personal projects is the same reason that it's dominating and likely to eclipse autocompletion tools.

## Autocomplete is not all you need

Tools that began doing autocompletion using an LLM the developer's current context, such as [GitHub Copilot](/blog/github-copilot) or [Codeium](/blog/codeium-review) have a limit baked in: they're still expecting developers to write code.

Cursor had a bolder vision, and recognizes that the opportunity is not just about the LLMs available today, but ever advancing models specialized toward code generation and working with codebases.

Cursor changed the primary interface to natural language.

## Natural language interfaces are the key disruptor

In retrospect, I think the Cursor team's decision to use VSCode as the starting point makes a lot of sense. VSCode was is a fully featured IDE, meaning that it puts code front and center and offers programmers many rich tools for understanding, finding, refactoring, debugging and writing code.

Cursor knew to focus on its core value proposition - allowing experienced developers to describe the changes they want in plain English - a massive force multiplier for those with enough background to know how they want their software architected.

A few well written paragraphs might take a developer a few minutes or less to write - but when they submit that prompt, Cursor is orchestrating the work of LLMs and the IDE itself to rapidly edit, refactor, and create new files as needed to reach the requested state.

I wish I could say otherwise, but I cannot code as fast as Claude 3.5 Sonnet when hooked up to the IDE's API. And now that I've been experimenting with this way of developing software for the past several months, I'm not sure I'd want to.

## Cursor is likely a preview of where the field is headed

As my colleague, Roie Schwaber-Cohen and I argue in [Yes, Coding is changing, and humans are more important than ever](https://thenewstack.io/ai-coding-human-engineers-are-more-important-than-ever/), this shift is likely a boon for developers. It's already helping experienced developers create software faster.

I remain fully responsible for the software that I'm shipping, and whether it's well architected, secure, and understandable by those who will use and maintain it. Yet, I am no longer writing every single line of code myself.

## Things Cursor does very well.

Here are the things I believe we're already seeing competitors try to replicate. For example, this week Cursor raised 60m in funding, and Codeium began advertising their new Forge feature, which is a step toward the fully-codebase aware natural language interfaces were discussing here.

### At file, folder, codebase mentions

If copy and pasting back and forth between ChatGPT.com is crawling, then Cursor's interface is sprinting.

Being able to discuss the code, architecture, a single file, or to tell Cursor to use a file as inspiration when making other changes is my favorite feature of Cursor.

### Model interop

LLMs and models appear to be heading toward rapid commoditization. They're at least currently advancing rapidly enough that Cursor recognized you want to be able to change models on a per-request basis.

If Claude 3.5 Sonnet, the current champ of commercial LLMs for coding at this moment, failed to meet your needs in that last refactor request - you could try the exact same prompt with ChatGPT 4o in one click.

I'm seeing this pattern in many popular projects. Users want control over which model they're running and want to be able to swap them quickly or provide their own API keys as needed to avoid limits.

### Composer interface

The idea here is even one more step toward coding in natural language. In this mode, developers can still at-mention files, but the idea is that pretty soon they won't need to. They'll just talk about what they want done, and review the code changes that resulted.

In actuality, this is currently hit or miss, undergoing rapid development and a glimpse at how the killer Dev UX is likely codebase-wide natural language interfaces for rapid refactoring.

I expect this to be the next big thing, and I expect the Cursor team does, too. When I first saw GitHub's Copilot Workspaces teased, I was excited, as a person who has a few hundred open source repositories themselves and who maintains additional repositories for their employer.

Once all the kinks are worked out, I look forward to being able to rip through a dozen repos that all need fixes, updates, patches over my morning coffee.

databases

## Table of contents

## vector database comparison: Pinecone vs FAISS

This page contains a detailed comparison of the Pinecone and FAISS vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Pinecone | FAISS | | ---------| -------------| -------------| | Local Deployment | ❌ | ✅ | | Cloud Deployment | ✅ | ❌ | | On - Premises Deployment | ❌ | ✅ |

## Scalability | Feature | Pinecone | FAISS | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ❌ | | Vertical Scaling | ❌ | ✅ | | Distributed Architecture | ✅ | ❌ |

## Data Management | Feature | Pinecone | FAISS | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ❌ | ❌ |

## Security | Feature | Pinecone | FAISS | | ---------| -------------| -------------| | Authentication | ✅ | ❌ | | Data Encryption | ✅ | ❌ | | Access Control | ✅ | ❌ |

## Vector Similarity Search | Feature | Pinecone | FAISS | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean | Euclidean, Inner Product | | ANN Algorithms | Custom | IVF, HNSW, Flat | | Filtering | ✅ | ❌ | | Post-Processing | ❌ | ❌ |

## Integration and API | Feature | Pinecone | FAISS | |---------|-------------|-------------| | Language SDKs | Python |  | | REST API | ✅ | ❌ | | GraphQL API | ❌ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Pinecone | FAISS | |---------|-------------|-------------| | Open-Source | ❌ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Pinecone | FAISS | |---------|-------------|-------------| | Free Tier | ✅ | ✅ | | Pay-as-you-go | ✅ | ❌ | | Enterprise Plans | ✅ | ❌ |

databases

## Table of contents

## vector database comparison: Pinecone vs Weaviate

This page contains a detailed comparison of the Pinecone and Weaviate vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Pinecone | Weaviate | | ---------| -------------| -------------| | Local Deployment | ❌ | ✅ | | Cloud Deployment | ✅ | ✅ | | On - Premises Deployment | ❌ | ✅ |

## Scalability | Feature | Pinecone | Weaviate | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ✅ | | Vertical Scaling | ❌ | ✅ | | Distributed Architecture | ✅ | ✅ |

## Data Management | Feature | Pinecone | Weaviate | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ❌ | ✅ |

## Security | Feature | Pinecone | Weaviate | | ---------| -------------| -------------| | Authentication | ✅ | ✅ | | Data Encryption | ✅ | ✅ | | Access Control | ✅ | ✅ |

## Vector Similarity Search | Feature | Pinecone | Weaviate | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean | Cosine, Euclidean, Jaccard | | ANN Algorithms | Custom | HNSW, Beam Search | | Filtering | ✅ | ✅ | | Post-Processing | ❌ | ✅ |

## Integration and API | Feature | Pinecone | Weaviate | |---------|-------------|-------------| | Language SDKs | Python | Python, Go, JavaScript | | REST API | ✅ | ✅ | | GraphQL API | ❌ | ✅ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Pinecone | Weaviate | |---------|-------------|-------------| | Open-Source | ❌ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Pinecone | Weaviate | |---------|-------------|-------------| | Free Tier | ✅ | ❌ | | Pay-as-you-go | ✅ | ❌ | | Enterprise Plans | ✅ | ✅ |

result.gif'

## What is this?

[`automations` is a collection of shell scripts](https://github.com/zackproser/automations) that automatically handle git operations, provide local code reviews, pull requests, and more!

These scripts leverage some of my favorite Go CLI libraries from the talented folks of [`github.com/charmbracelet`](https://github.com/charmbracelet).

I wrote them for fun and also to save me time, and, as I continue to use them and polish them every time I find an issue, I am happy to report that they are indeed fun to use. I'm pretty sure they're making me faster as well.

## Current automations

With that out of the way, let's get into the automations themselves:

[`autogit`](https://github.com/zackproser/automations#autogit)

ensures you're working with the latest code locally

[`autoreview`](https://github.com/zackproser/automations#autoreview)

get a senior

level code review in your terminal

[`autocommitmessage`](https://github.com/zackproser/automations#autocommitmessage)

always write excellent, clear commit messages

[`autopullrequest`](https://github.com/zackproser/automations#autopullrequest)

open pull requests with clear titles and descriptions

## autogit

When you change into a new local repository, autogit runs (thanks to an alias you've configured for your shell) and ensures that your default branch is up to date with the remote. This helps prevent you from opening pull requests with stale changes.

## autoreview

Want a Senior-level code review right in your terminal to help you catch all the low-hanging fruit before you bug a human teammate? `autoreview` does just that.

Here's an example of the kind of review it can produce, and it writes the file to `~/.autoreview` so that you can revisit them or lift code from them.

## autocommitmessage

So far, this may be the automation that has saved me the most time. I love the way it works because it's not just stamping out some low quality commit message that nobody can parse out later.

It actually describes the context of your changes pretty effectively, so it's currently hitting the sweet spot (at least for me) of combining increased speed with increased quality.

Typing my personal alias for it, `gcai`, is so fast there's no excuse for writing "check in latest" anymore.

## autopullrequest

This is the most recent automation, but it's pretty promising so far. It uses the official GitHub CLI, `gh` to open pull requests. It uses ChatGPT4 to generate clear, succinct and professional PR descriptions and titles, and then it opens the pull request for you.

Here's an example pull request it recently opened for me:

## What's next?

I'll continue improving the docs and the automations themselves. I hope to add some more automations in the future, too. I want to look into autocompletion functionality and adding installation instructions for various shells.

## A request

If you found this interesting or useful, please consider sharing it with someone you know who may be interested in the project, or sign-up for my newsletter to receive tips and projects like this as soon as I publish them. Thanks for reading!

verified

commit

5

keyboard

cam

commit

signing

yubikey

If you're like me - you can't type your complex password correctly when your entire team is staring at you on a pair coding call.

When you're done reading this post, you'll never need to again. Instead, you'll tap your Yubikey to execute a sudo command without ever touching a password prompt.

Next, I'll show you how to automatically sign your GitHub commits with the private PGP key *that only exists physically on my Yubikey 5 NFC and which cannot be exported from the device*. This means you cannot author a verified commit on GitHub as me unless you're in the room with me - like right meow - and have admin access to one of my machines.

With these two security and workflow enhancements in place, developing software is much faster and less stressful, even if you're doing it under the withering glare of the curious and malicious.

Not bad for one little device! Along the way, my hope is that you'll discover something or other about the Yubikey you didn't know before.

This guide will focus on the Yubikey 5 NFC model, and contemplate setting everything up on an Ubuntu machine, but this will also work for Mac OSX and Windows. I'll create follow-up guides for them, if there's sufficient interest.

## The challenge: writing software in public, securely

As ambitious software developers, technologists, and creators looking to share knowledge, we actively seek opportunities to stream our terminal, whether it's to our team when working through an issue together,  or to thousands of people in real-time on Twitch or YouTube.

How can we possibly hope to type sudo passwords and develop software in public securely? I had this question myself when I first started [streaming Golang development on Twitch](https://twitch.tv/zackproser).

## No, seriously - I pointed a high resolution zooming camera at my hands and connected it to the public internet

Just in case there were any questions about the stakes! I enjoy doing things the hard way, so I decided to set up a separate Linux cam on a post at the back of my desk to zoom in and focus on my hands on the keyboard while I develop.

I want folks to see how I use the keyboard and the weird patterns my fingers have gnarled into after many decades of typing in different contexts, because I think it makes the streaming content somewhat more interesting. Here's roughly what that looks like:

Many folks I've paired with over the years have commented that the way my hands and fingers move is strange, but when I'm properly caffeinated and focused, I can type around 112 WPM with high accuracy.

Of course, this presents a critical problem: my typing is of equal interest to viewers who want to learn more about my workflow, and would-be cyber-criminals.

## Don't miss the next post!

## Replacing your `sudo` password with a Yubikey tap

The best guide I found to doing this was written by bashbunni [here](https://dev.to/bashbunni/set-up-yubikey-for-passwordless-sudo-authentication-4h5o), and I've slightly adapted the steps below.

**Important** - Please read all the steps before proceeding. It's very important to NOT CLOSE your PAM configuration editor buffer until you have confirmed that `sudo` works as expected in a different terminal. *Failure to follow this advice could lead to pain. *

### Step 1. Install dependencies

### Step 2. Create a Yubico config directory

### Step 3. Plug in your Yubikey

You should see the light flash.

### Step 4. Add your Yubikey to the list of accepted Yubikeys

 Enter your pin if one is set for the key, then touch the key when the key's light blinks.

### Step 5. Configure PAM

Find the section of the file that says `@include common-auth` and add the following line above it:

Here's what your file might look like now:

### Step 6. Verify

Now - DO NOT CLOSE your original PAM editing buffer, but instead open a new terminal window and type:

and hit enter. When you do, you should see your terminal "hang" and your connected Yubikey's light start to blink. This means PAM is waiting on you to tap your Yubikey! Do so and you should see:

printed in your new terminal. If you do, you're all set and can now enjoy passwordless sudo.

The reason this setup works is thanks to Linux's Pluggable Authentication Module (PAM) system. PAM doesn't do authentication, but it allows you to add things that can - such as your Yubikey!

## Understanding how GitHub commit signing verification with a Yubikey works

Next up, we're going to ensure that nobody can author a verified commit as us on GitHub. Here's what that entails in a nutshell:

1. We generate a new GPG keypair *on our Yubikey itself*. The private key resides on the Yubikey and cannot be exported.

2. We tell our local git installation to use this private Yubikey-jailed key to sign our git commits automatically

3. We export and provide the public key to GitHub.

4. When we push up signed commits, GitHub automatically verifies them (if the signature matches that of our GPG pair) and adds the `verified` badge to our commits, which looks like this:

If the Yubikey is plugged into my machine when I execute a `git commit` command, my commit is automatically signed by the private PGP key residing on my Yubikey. No more fat fingering my complex password and having to rewrite my git commit message.

If the Yubikey is not plugged into my machine, git commit signing fails.

This is really powerful, and having used it now for several months, I love it and can't return to my previous ways.

Let's look at a sequence diagram to understand how this flow works:

Commit signing verification is an important step to take if you participate in open source development because it is trivially easy to [spoof someone's git commits](https://blog.gruntwork.io/how-to-spoof-any-user-on-github-and-what-to-do-to-prevent-it-e237e95b8deb) without this extra layer of verification protection in place.

## How to configure automatic GitHub commit signing verification with Yubikey

### Step 1. Install dependencies

See [Yubico's official guide](https://support.yubico.com/hc/en

us/articles/360016649039

Installing

Yubico

Software

on

Linux).

### Step 2. Generate the keypair on your Yubikey

Follow [Yubico's official guide](https://support.yubico.com/hc/en-us/articles/360013790259-Using-Your-YubiKey-with-OpenPGP) - and scroll down to the find the second option:

"Generating Your PGP Key directly on Your YubiKey"

### Step 3. Tell your local git to use the Yubikey-contained private key for commit signing

### Step 4. Give GitHub your public key

You can follow [GitHub's official guide. ](https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account)

Once you've completed these steps, author any commit on a local project and push it up to GitHub. You will originally be prompted for you Yubikey's pin, but once you enter that - you won't need to do it again for the length of your session.

Check that your latest commit is showing the verified badge on GitHub, at which point, you're all done!

## Insight: the developer experience is paramount

I needed a solution that was both highly secure and also extremely low friction to my daily workflow. Something that I would actually *enjoy using* because it is so seamless and reliable.

What makes this setup so ideal is that it does not introduce additional overhead, complexity or maintenance headaches for the developer implementing it - instead, *it actually simplifies and eases common tasks*. It **takes away tedious things you normally have to do** while still making you more secure. See also: "frictionless security" . This is the way.

### Thanks for reading

and a request!

Thanks so much for taking the time to read this post. If you found it at all helpful, I hope you will please consider subscribing to my newsletter below, or sharing this post with a friend. Thank you so much for your support!

favorite

ai

uplifting

with

ai

tools

dont

and

human

direct

 As of October 10th, 2023, ChatGPT4 and Codeium are all I need to make excellent progress and have fun doing it.

As of October 10th, 2023, the Generative AI hype cycle is still in full-swing and there are more startups with their own developer-focused AI-assisted coding tools than ever before. Here's why I'm still perfectly content with ChatGPT4 (with a Plus subscription for $20 per month) and [Codeium, which I've reviewed here](/blog/codeium-review) for code completion.

## They are available everywhere

ChatGPT4 can be opened from a browser anywhere, even on internet-connected machines I don't own: `chat.openai.com` is part of my muscle memory now, and I once I log in, my entire conversational history is available to me. Now that ChatGPT4 is available on Android, it's truly with me wherever I go.

## They are low

friction

Now that ChatGPT4 is on my phone, I can start up a new conversation when I'm walking and away from my desk. Some days, after working all day and winding down for sleep, I'll still have a couple of exciting creative threads I don't want to miss out on, so I'll quickly jot or speak a paragraph of context into a new new GPT4 chat thread to get it whirring away on the idea. I can either tune it by giving it more feedback or just pass out and pick up the conversation the next day.

I don't always stick to stock ChatGPT4 form-factors, however. Charmbracelet's [`mods`](https://github.com/charmbraclet/mods) wrapper is the highest quality and most delightful tool I've found for working with GPT4 in your unix pipes or just asking quick questions in your terminal such as, "Remind me of the `psql` command to connect to my Postgres host".

Being able to pipe your entire Python file to `mods` and ask it to help you find the bug is truly accelerating.

Codium works like a dream once you get it installed. I tend to use Neovim by preference but also work with VSCode - once you're over the initial installation and auth hurdles, it "just works".

## Most everything else I've tried doesn't work

No disrespect to these tools or the teams behind them. I do believe the potential is there and that many of them will become very successful in due time once the initial kinks are worked out.

But I've spent a great deal of time experimenting with these across various form factors: an Ubuntu desktop, my daily driver Linux laptop, a standard MacBook pro, and the reality is that the code or tests or suggestions they output are often off the mark.

## ChatGPT4 extends my capabilities with minimal fuss

Since ChatGPT4 is available in the browser, I can access it from any machine with an internet connection, even if I'm traveling. Since it's now also available as an Android app, I can also reference past conversations and start new ones on my phone.

The Android app has a long way to go until it's perfect, yet it does already support speech to text, making for the lowest possible friction entrypoint to a new app idea, architectural discussion or line of inquiry to help cement my understanding of a topic I'm learning more about.

## They are complimentary

ChatGPT4 excels at having long-winded discussions with me about the many ways I might implement desired functionality in one of my applications or side projects. It often suggests things that I would have missed on my first pass, such as the fact that Vercel has deployment hooks I can take advantage of, and it's especially useful once the project is underway.

I can say things like:

> I'm changing the data model yet again now that I understand how I want this UX to work - drop these fields from the posts table, add these to the images table and re-generate the base SQL migrations I run to scaffold the app.

I think of and treat ChatGPT4 as a senior level technical peer.

I do sometimes ask it to generate code for me as a starting point for a new component, or to explain a TypeScript error that is baking my noodle, but it's main value is in being that intelligent always-available coding partner I can talk through issues with.

Meanwhile, Codium runs in my actual IDE and is one of the best tools I've found at code-completion - it does a better job of just about anything I've evaluated at grokking the importance of surrounding code and comments and its suggestions are often scarily spot-on, which means that it saves me a couple seconds here and there, closing HTML tags for me, finishing up that convenience JavaScript function I'm writing, even completing my thought as I'm filling in a README.

That's the other key feature of Codium that makes it such a winner for me - it's with me everywhere and it can suggest completions in any context ranging from prose, to TOML, to Python, to TypeScript, to Go, to Dockerfiles, to YAML, and on and on.

With GPT4 as my coding buddy who has the memory of an elephant and who can deconstruct even the nastiest stack-traces or log dumps on command, and Codium helping me to save little bits of time here and there but constantly, I have settled on a workflow that accelerates me and, perhaps more importantly, increases my enjoyment of development, which I spent the vast majority of my time doing.

## Looking forward and what I'm still missing

I have no doubt that the current generation of developer-focused AI tools are going to continue improving at a rapid pace. ChatGPT itself has seen nothing but enhancement at breakneck speed since I started using it and I haven't even gotten my hands on its multi-modal (vision, hearing, etc) capabilities yet.

However, even with excellent wrappers such as `mods`, which I've mentioned above, what I find myself missing is the ability for ChatGPT4 to read and see my entire codebase when I'm working on a given project.

The quality of its answers are occasionally hobbled by its inability to load my entire codebase into its context, which leads to it generating more generic sample code than it really needs to.

I'm confident with additional work and a litle time, it won't be long until ChatGPT4 or one of its competitors is able to say: "The reason your current Jest test is choking on that import statement is because you're missing this specific setting and value in your .tsconfig file".

I'm able to get that information out of it now, but it just takes a good bit of careful prompting and more copy/paste than I would ideally like. What I really want is the helpful daemon looking over my shoulder, who is smart enough to know when to raise its hand to point out something that's going to cause the build to fail, and when to keep quiet because even if it knows better that's just my personal coding style preference so better to leave it alone.

We're not quite there yet, but all of the base ingredients to create this experience are.

Indeed, many different companies both large and small are sprinting full-tilt toward this experience, as I've written about recently, but there's still quite a way to go until these tools present uniformly smooth experiences to their end users:

[GitHub Copilot review](/blog/github

copilot

review)

[The top bugs all AI developer tools have right now](/blog/top

ai

dev

tools

bugs)

[Codeium review](/blog/codeium

review)

[CodiumAI PR agent for eased GitHub maintenance](/blog/codeium

ai

pinecone

pr

agent)

[Can ChatGPT4 help me complete side projects more quickly? ](/blog/chatgpt

4

experiment

and

ai

development)

xargs

swagger

pr

---

## Introduction

[Pageripper is a commercial API](https://rapidapi.com/zackproser/api/pageripper/) that extracts data from webpages, even if they're rendered with Javascript.

In this post, I'll detail the Continuous Integration and Continuous Delivery (CI/CD) automations I've configured via GitHub Actions for [my Pageripper project](https://github.com/zackproser/pageripper-v2), explain how they work and why they make working Pageripper delightful (and fast).

## Why care about developer experience?

Working on well

automated repositories is delightful.

Focusing on the logic and UX of my changes allows me to do my best work, while the repository handles the tedium of running tests and publishing releases.

At Gruntwork.io, we published [git-xargs](https://github.com/gruntwork-io/git-xargs), a tool for multiplexing changes across many repositories simultaneously.

Working on this project was a delight, because we spent the time to implement an excellent CI/CD pipeline that handled running tests and publishing releases.

As a result, reviewing and merging pull requests, adding new features and fixing bugs was significantly snappier, and felt easier to do.

So, why should you care about developer experience? Let's consider what happens when it's a mess...

## What happens when your developer experience sucks

I've seen work slow to a crawl because the repositories were a mess: long-running tests that took over 45 minutes to complete a run and that were flaky.

Even well-intentioned and experienced developers experience a slow-down effect when dealing with repositories that lack CI/CD or have problematic, flaky builds and ultimately untrustable pipelines.

Taking the time to correctly set up your repositories up front is a case of slowing down to go faster. Ultimately, it's a matter of project velocity.

Developer time is limited and expensive, so making sure the path is clear for the production line is critical to success.

## What are CI/CD automations?

Continuous Integration is about constantly merging into your project verified and tested units of incremental value.

You add a new feature, test it locally and then push it up on a branch and open a pull request.

Without needing to do anything else, the automation workflows kick in and run the projects tests for you, verifying you haven't broken anything.

If the tests pass, you merge them in, which prompts more automation to deploy your latest code to production.

In this way, developers get to focus on logic, features, UX and doing the right thing from a code perspective. The pipeline instruments the guardrails that everyone needs in order to move very quickly.

And that's what this is all about at the end of the day. Mature pipelines allow you to move faster. Safety begets speed.

## Pageripper's automations

Let's take a look at the workflows I've configured for Pageripper.

### On pull request

**Jest unit tests are run**

Tests run on every pull request, and tests run quickly. Unit tests are defined in jest.

Developers get feedback on their changes in a minute or less, tightening the overall iteration cycle.

npm build**

It's possible for your unit tests to pass but your application build to still fail due to any number of things: from dependency issues to incorrect configurations and more.

For that reason, whenever tests are run, the workflow also runs an `npm build` to ensure the application builds successfully.

docker build**

The Pageripper API is Dockerized because it's running on AWS Elastic Container Service (ECS). Because Pageripper uses Puppeteer, which uses Chromium or an installation of the Chrome browser, building the Docker image is a bit involved and also takes a while.

I want to know immediately if the build is broken, so if and only if the tests all pass, then a test docker build is done via GitHub actions as well.

OpenAPI spec validation**

For consistency and the many downstream benefits (documentation and SDK generation, for example), I maintain an OpenAPI spec for Pageripper.

On every pull request, this spec is validated to ensure no changes or typos broke anything.

This spec is used for a couple of things:

Generating the Swagger UI for the API documentation that is hosted on GitHub pages and integrated with the repository

Generating the test requests and the documentation and examples on RapidAPI, where Pageripper is listed

Running dredd to validate that the API correctly implements the spec

Pulumi preview**

Pageripper uses Pulumi and Infrastructure as Code (IaC) to manage not just the packaging of the application into a Docker container, but the orchestration of all other supporting infrastructure and AWS resources that comprise a functioning production API service.

This means that on every pull request we can run `pulumi preview` to get a delta of the changes that Pulumi will make to our AWS account on the next deployment.

To further reduce friction, I've configured the Pulumi GitHub application to run on my repository, so that the output of `pulumi preview` can be added directly to my pull request as a comment:

### On merge to main

**OpenAPI spec is automatically published**

A workflow converts the latest OpenAPI spec into a Swagger UI site that details the various API endpoints, and expected request and response format:

Pulumi deployment to AWS**

The latest changes are deployed to AWS via the `pulumi update` command. This means that what's at the HEAD of the repository's main branch is what's in production at any given time.

This also means that developers never need to: * Worry about maintaining the credentials for deployments themselves * Worry about maintaining the deployment pipeline themselves via scripts * Worry about their team members being able to follow the same deployment process * Worry about scheduling deployments for certain days of the week - they can deploy multiple times a day, with confidence

## Thanks for reading

If you're interested in automating more of your API development lifecycle, have a look at the workflows in the [Pageripper repository](https://github.com/zackproser/pageripper-v2).

And if you need help configuring CI/CD for the ultimate velocity and developer producitvity, feel free to reach out!

crt

monitor

 A photo of myself working on the previous iteration of this website, which I mostly hand-rolled in order to do things the hard way

Note, this article was written in reference to the previous iteration of my portfolio site.

See also my follow-up piece, [*Maintaining this site no longer fucking sucks*](/blog/maintaining-this-site-no-longer-fucking-sucks).

---

Maintaining this site FUCKING SUCKS.

You'd think that with all the advances in technology, the fact that I've been doing this for longer than a decade now, and the sheer number of frameworks and tools available, that maintaining a highly customized personal website and blog would be a breeze. It isn't.

First, let's talk about the DNS management. You would think that something as simple as pointing a domain name to an IP address would be straightforward, but no. There's always some bizarre propagation delay or weird caching issue that makes you question your sanity.

Oh, you want all your images to be cached at the edge so they're super snappy for everyone visiting your page, but you also want to bust your cache each time you publish a new version of your site? Fuck you! Figure it out and do it yourself.

GoDaddy DNS, Domain transfers, Cloudflare accounts and cache settings and tier-locked behavior to worry about. Figuring out which device still has that 2FA account on it so that I can log in and change the settings. Pawing through the devtools output as I debug why my images aren't being cached as expected. What a goddamned nightmare.

Then there are the Node.js and Vue.js version upgrades, which feel like a relentless, never-ending battle. I swear, it's as if every single time I sit down to write a blog post, there's a new version of something absolutely critical to my site lurking in the shadows, waiting to pounce. With each upgrade comes the inevitable breaking changes, followed by the torturous debugging sessions that send me on a wild goose chase through the darkest, most obscure corners of Stack Overflow. And just when I think I've tamed the beast, another update appears, accompanied by a fresh set of dependency conflicts, deprecated methods, and newly introduced bugs that leave me questioning my very existence as a developer. Manual labor wasn't *that* bad.

Security upgrades are another beast entirely. Just when I think I've got everything under control, I load up GitHub to review and merge my latest pull request, but I'm beset instead by a dozen new CVE issues that dependabot is trying to patch via its own programmatic pull requests. Cue the panic, followed by the mad dash to patch and test the affected components. The other week I saw a guy drive up to my neighbor's house with a giant powerwashing setup and proceed to clean the entire exterior himself over the course of 4 hours. I could do that, too.

And let's not even get started on dependency hell. Managing the compatibility between my Ubuntu machine, the C libraries and dependencies needed to create the hyper-performant pre-processor that can convert SASS into raw CSS, the Vue.js version 2 site I built ages ago, and the increasingly complex web of interconnected libraries is like playing a never-ending game of Jenga. In some cases I sit down and run `npm run dev` after changing nothing and end up in a situation where my installed SASS-processing dependency issues prevent the local server from even coming up. Good luck googling for solutions when you're that far out of date on your primary framework!

Of course I had to write my own blogging system. Of course it had to work within Vue 2 and of course I had to use the nuxt config file, which is really a separate complex Node.js program, to do most of the heavy lifting to allow the final product of all this tedium to be at least somewhat reasonably search engine optimized. Of course I didn't document it either, because fuck that guy who's going to deal with it next.

I know that I could put this burden down. I have a mentor making excellent and sober use of Squarespace for his professional domain - and the results look great. I read his blog posts myself and think that they look good! It doesn't have to be like this.

But here's the thing: despite all the frustration, the hair-pulling, and the curse words muttered under my breath, there's something strangely satisfying about tackling these challenges head-on. In the process of keeping this site running, I've learned even more about full-stack development than I would have otherwise, and, more importantly, I've continued developing callouses as I encounter and solve so many common web-based pain points that are totally relevant to my day job, too.

Sure, the number of times that I've sat down with a finished blog post that I want to share with the world only to be thwarted last minute by surprise:

DNS issues

Node.js or package.json or Vue.js or Nuxt.js issues or Ubuntu C library issues

CVEs that force my to bump some obscure dependency past the last version that works in my current setup

Debugging and customizing pre

built CSS frameworks

...now truly defies my tallying, because maintaining this site fucking sucks!

And that's exactly why I do it. It's one of the best projects I've ever created.

with

git

What do you do if you need to run git in a context where you cannot install whatever you want? What if you need to run git in a serverless environment that only supports JavaScript?

In [How to run background jobs on Vercel without a queue](/blog/how-to-run-background-jobs-on-vercel-without-a-queue), I wrote about how to keep the API route that accepts new jobs snappy and responsive using fire-and-forget functions.

In this post, I'll demonstrate how you can use `isomorphic-git` even where you can't install git via a traditional package manager like `apt`, ship a new Docker image or update the build script. In this case, we're running git within our Vercel serverless functions.

## Table of contents

## The problem

I'm developing a tool for myself to streamline my blog post authoring workflow, called [Panthalia](https://github.com/zackproser/panthalia).

The mobile client allows me to author new blog posts rapidly, because I tend to have several new post ideas at one time. This means the name of the game is capture speed.

 The workflow that Panthalia performs to make changes to my website that hosts my blog

As you can see in the above flow diagram, Panthalia makes changes to my site via git. It clones my site, adds new posts, pushes changes up on a new branch and uses a GitHub token to open pull requests.

## Running git where you can't run git

Git is required in this flow. Sure, there are some funky ways I could leverage GitHub's API to modify content out of band, but for reliability and atomic operations, I wanted to use git.

My portfolio site which hosts my blog, and Panthalia, which modifies my site via git, are both Next.js projects deployed on Vercel.

This means that any API routes I create in these two apps will be automatically split out into Vercel serverless functions upon deployment.

I can't modify their build scripts. I can't use apt to install git. I can't update a Docker image to include the tools I need. It's JavaScript or bust.

Enter the impressive [`isomorphic-git`](https://www.npmjs.com/package/isomorphic-git) project. You can `npm install` or `pnpm add isomorphic-git` like any other package to install it.

`isomorphic-git` allows you to clone, fetch, stage and commit changes and operate on branches using a familiar API.

## Git operations in JavaScript

Let's take a look at the method that clones my site and creates a new branch based on the new blog post title:

Here are the convenience methods I created to simplify wiping any pre-existing clones and starting afresh.

## Git in the time of serverless

Vercel's serverless functions do expose a filesystem that you can write to. I set a `CLONE_PATH` environment variable that defaults to `/tmp/repo`.

There are also timing considerations. Vercel functions can run for up to 5 minutes on the Pro plan, so I don't wany any particular API route's work to be terminated due to a timeout.

That's why I perform a shallow clone - meaning only the latest commit - and I configure the `singleBranch` option to only grab the main branch.

Given that my portfolio site has a decent amount of history and a lot of images, these two changes cut down cloning time from a few minutes to about 30 seconds.

There's also the asynchronous nature of the compute environment Vercel functions are running in.

One of the reasons I do a full wipe and clone is that I always want my operations to be idempotent, but I can't be sure whether a given serverless function invocation will or will not have access to a previously cloned repo in the temporary directory.

## Authentication considerations with JavaScript-based git

My portfolio site, [github.com/zackproser/portfolio](https://github.com/zackproser/portfolio) is open-source and public, so I don't need any credentials to clone it.

However, once Panthalia has cloned my repo, created a new branch and committed the changes to add my new blog post, it does need to present a GitHub token when pushing my changes.

You present a token using the `onAuth` callback in `isomorphic-git`. I recommend storing your GitHub token as an environment variable locally and in Vercel:

## Wrapping up

I knew that I wanted to ship Next.js and deploy it on Vercel because the developer experience is so delightful (and fast). This meant that I couldn't use the traditional git client in the JavaScript serverless function context.

The [isomorphic-git project](https://isomorphic-git.org/) allows me to implement a complete programmatic git workflow in my Next.js app running on Vercel, and it works very well.

Server-Side Rendered Page

This is an example of a server-side rendered page.

Link 1

Link 2

Link 3

html

Client-Side Rendered Page

bash curl -X POST \ -H 'Content-type: application/json' \ -d '{"options": {"includeUrls": true, "waitUntil":"domcontentloaded"}, "url": "https://nytimes.com"}' \

databases

## Table of contents

## vector database comparison: Weaviate vs Chroma

This page contains a detailed comparison of the Weaviate and Chroma vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Weaviate | Chroma | | ---------| -------------| -------------| | Local Deployment | ✅ | ✅ | | Cloud Deployment | ✅ | ✅ | | On - Premises Deployment | ✅ | ✅ |

## Scalability | Feature | Weaviate | Chroma | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ✅ | | Vertical Scaling | ✅ | ✅ | | Distributed Architecture | ✅ | ✅ |

## Data Management | Feature | Weaviate | Chroma | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ✅ | ✅ |

## Security | Feature | Weaviate | Chroma | | ---------| -------------| -------------| | Authentication | ✅ | ✅ | | Data Encryption | ✅ | ✅ | | Access Control | ✅ | ✅ |

## Vector Similarity Search | Feature | Weaviate | Chroma | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean, Jaccard | Cosine, Euclidean | | ANN Algorithms | HNSW, Beam Search | Custom | | Filtering | ✅ | ✅ | | Post-Processing | ✅ | ✅ |

## Integration and API | Feature | Weaviate | Chroma | |---------|-------------|-------------| | Language SDKs | Python, Go, JavaScript | Python | | REST API | ✅ | ✅ | | GraphQL API | ✅ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Weaviate | Chroma | |---------|-------------|-------------| | Open-Source | ✅ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Weaviate | Chroma | |---------|-------------|-------------| | Free Tier | ❌ | ❌ | | Pay-as-you-go | ❌ | ✅ | | Enterprise Plans | ✅ | ✅ |

screens/catfacts

screens/

Visit the project page on GitHub

## What Developers Are Saying About This Project

It was important to me to get community buy-in and feedback on this project from early on. Having finally reached a close developer friend after giving him an exclusive demo of Super CatFacts Attack, he had this to say about this sms-interfacing, child-process-forking open source project:

> "F#@KING STOP WITH THE F#@KING CATFACTS DUDE. SERIOUSLY. THIS WAS NOT FUNNY AFTER THE FIRST 30 MINUTES. I HAD TO PAY $75 TO AT&T ON MY ONE DAY OFF TO CHANGE MY PHONE NUMBER. I GET THE CONCEPT OF THE PRANK AND WE'RE FRIENDS BUT YOU ARE SERIOUSLY PUSHING ME RIGHT NOW AND I DON"T APPRECIATE IT. DO NOT TEXT ME AGAIN."

## Hear the Super CatFacts Call Menu

## Twilio + Node.js + Forked Child Processes = Pranking Bliss

### What is this?

Start and stop elaborate CatFacts pranks on your friends and family by simply texting this app with your victim's number.

This project was a great excuse to explore some concepts I had been wanting to play with for a while:

Using SMS commands from your phone to control a server, which preserves state

Locking down said server so that it only responds to YOU or your trusted friends

Managing child processes by tagging them with custom properties (like a rare white rhino) so we can look them up later by tag AND KILL THEM

Leveraging this forked process model in such a way that we can simulataneously attack multiple targets with a single server

Weaving together Twilio's sms and telephony functionality along with various Twiml verbs to create a rich user experience

Using an Express app to generate Twiml that responds to calls and texts and plays static sound files served by the server

Using a simple json file to configure our server

Passing environment variables to a node script

## What You'll Learn

This tutorial will also serve as a great introduction to using Twilio in concept, and specifically using Twilio's Node.js SDK. This is a good tutorial for anyone that's been wanting to use Twilio's service but just hasn't gotten around to it.

After following along with building Super CatFacts Attack, it will be clear to you how you could build actually useful services using the functionality that Twilio provides. Examples of great apps you could build implementing the concepts described in this tutorial include:

Services that do a lot of intensive crunching or network / service based lookups and return you condensed information on demand to your phone via sms

Apps that kick off tasks that are too complicated to manage on your phone while you're busy

with a simple sms command. The app would publish the result of its work as a public page and send you the result as a bitly link

Voting systems: this project demonstrates how to accept sms messages via Twilio and handle them in your server. You could easily build a voting system or custom sweepstakes event system following the same pattern

Mass communication systems: send a text containing special commands to your server, and have your server notify all your relevant friends, contacts, family members more efficiently than you could on your own

SMS based games

send texts as commands, game actions, in a wide distirbuted scavenger

hunt

style game

## Enough Productivity. I DEMAND CATFACTS!

### Super CatFacts Attack Workflow

You're out and about with some friends. You subtly send a text to a special contact you have saved in your phone - the body of your message is the phone number of the friend sitting next to you. The person you're texting isn't a person - it's a private server you own. Your server parses the phone number you sent in your sms - _and launches a CatFacts Attack against it._

Your friend receives a text message from a strange phone number: `**Thank you for subscribing to CatFacts! You will now receive fun facts about cats! >o<**`

WTF says your friend, after receiving the 10th fact in a row - this time with a message to call about their CatFacts account. **Maybe you should give them a call,** you suggest helpfully.

Your friend follows your advice and calls the CatFacts Call Center, where they get an IVR system that is similarly CatFacts-themed. They get a working menu and can either:

Request another CatFact to be immediately delivered to their phone

Pull up their account to determine why they were subscribed (it's because they love cats)

Request to have their account disabled (which will fail due to technical difficulties)

Hilarity ensues.

## Table of Contents

What you'll find in this post:

[Technical Overview](#technical

overview)

[An Important Twilio Precursor](#twilio

precursor)

[Handling Inbound SMS In Your Express App](#handling

inbound

sms)

[Handling Inbound Calls in Your Express App](#handling

inbound

calls)

[Generating Twiml with Node.js](#generating

twiml)

[Forking Child Processes (Starting Attacks)](#forking

child

processes)

[Keeping Track of Child Processes](#tracking

child

processes)

[Murdering Child Processes (Stopping Attacks)](#killing

child

processes)

### Technical Overview

Super CatFacts Attack is a Node.js server that exposes endpoints for receiving and processing commands that are POSTed by Twilio. In turn, SCFA returns valid Twiml to direct Twilio to send SMS messages, respond to telephone calls, and process phone keypad presses.

Blending all of this functionality together seamlessly, Super CatFacts Attack constitutes an epic, always-available pranking service you can quickly deploy to baffle and amuse your friends and family.

### An Important Twilio Precursor

**If you're not familiar with Twilio, read this paragraph first. ** Twilio abstracts away telephony integration for developers, allowing devs to use familiar HTTP concepts to send, receive and process SMS messages and phone calls.

When a user sends an SMS message to a Twilio number, Twilio will look up the associated endpoint (that you have built and exposed on a publically available server and specified in your Twilio account dashboard) and make a POST request to that endpoint.

The Twilio POST request contains all the information you'll need as a developer to integrate telephony into your web app or service: the phone number of the sending user, the body of their message, where their number was registered, etc. All of this information is organized nicely by Twilio so that you can access it in your request object. Meanwhile, you can use client libraries (like the 'twilio' node module I'm leveraging in this project) to make valid HTTP requests to Twilio to have Twilio send SMS messages or make phone calls in response as appopriate for your app's workflow.

Twilio also has a concept of Twiml (Twilio Markup Language), whose usage is demonstrated in this project and tutorial. That's really all you need to know for the purposes of this app - but if you want to read more in depth, check out [the excellent official Twilio documentation and examples here. ](https://www.twilio.com/docs/quickstart)

### Handling Inbound Twilio SMS in Your Express App

Handling Inbound Twilio-based SMS in your Express app is as simple as defining a route for Twilio to POST to when your associated Twilio number (the one you configure on your Twilio dashboard) receives an SMS message.

Twilio does a nice job of organizing all the data we need into an easy to use POST body - so we start by grabbing the phone number of the person who sent the SMS command (likely you! ).

We can pass this number into some custom authentication functions that inspect config.json to determine whether or not the given user is authorized to issue commands to the server.

We also check whether or not the target is already being attacked or not - while we want to support multiple simultaneous attacks on a variety of targets, we don't want to run multiple simultaneous attacks on one single target. We're insane, not evil.

### Handling Inbound Twilio Calls in Your Express App

Handling inbound calls is pretty similar, only we can use this an opportunity to start working with Twiml. Twilio Markup Language is what you should return on endpoints that Twilio hits in response to your users making a phone call to a Twilio number.

When Twilio connects a phone call, it hits the endpoint you'be specified on your account dashboard and parses the Twiml that you return in order to create the call experience (saying something, playing a sound, generating a menu that the user can dial their way through, etc). We're going to do all of these now. Let's start by defining the route that Twilio will make a POST request to when someone calls our associated Twilio phone number:

Our simple route accepts a POST request from Twilio, writes a Content-Type header and sends back a Twiml response, which will lead to the user hearing our menu interface spoken on the phone during their call. Let's now examine how that Twiml response is actually built:

The Node SDK (available in the npm module 'twilio') exposes helpful methods for easily building up Twiml. Once you create the initial Twiml response object, you build up its actual content by calling the various Twiml verb methods (say, play, gather, etc).

Notice that we can play static files served by Express, because we defined our static root when starting our server. We can easily get the path by using the server root as defined in our config.json plus the filename itself. The result is that the user gets perfectly timed cat noises - for a truly authentic experience.

The trickiest verb is **gather**. Similar in concept to the action property of an HTML form, we need to specify the action URL - where Twilio can go to get the response information following the user actually dialing a key for a particular menu item - in order for our IVR menu to work properly. Notice that I've specified a wildcard for the optional finishOnKey parameter. This will cause Twilio to stop listening for inputs after it gets a single keypress, which will make more sense when we next examine the function that actually handles our user's inputs:

You can get as fancy as you want to when parsing your user's touchtone input within a Twilio-based call menu like this. For my purposes, a simple switch break interface sufficed. By weaving together sounds and speech, you can create rich telephony experiences for your users.

I wanted to support admins sending in multiple attack commands and having the service intelligently manage them all - so that a single admin could simultaneously prank an entire dinner table's worth of suckers.

I achieve this by having a simple script, attack.js, that actually knows how to run an attack. It gets forked by the parent process everytime a valid attack request is received by the server:

Notice that we can pass an array of string arguments to the child process when we fork it. The child process can then access these variables and use them privately. The parent process and child process can also message each other back and forth as shown here. This allows us to write a service where the parent properly supervises and manages child processes based upon their self-reported states.

Forking our individual attacks in this way allows us to ensure that every victim gets their own private "attack context", starting with the initial introductory message and always proceeding one after another in the correct order, regardless of how many other people are being attacked by the same server at any given time.

Another key line here happens in the starting_attack case. Notice that I'm "tagging" the child process with the target_number it is actually running its attack on. I'm using this tag as a unique identifier, so that I can look up the child processes later on by their target number when an admin says it's time to stop attacking a given phone.

Now, let's take a look at a rudimentary manner of keeping tabs on these child processes so we can look them up by number later and kill them.

### Tracking Child Processes

Here's a simple way to keep tabs on the various simulatenous attacks that might be running at any given time. Each time we kick off a valid child process CatFacts attack, we store it in an app-level array:

 This makes it simple to look up processes later by their target_number property: 

When an admin sends a stop command with the number of the victim whose phone should no longer be bombarded, we need to look up the currently running attack using that number, and kill it:

screens/CanyonRunner

Aerial

screens/CanyonRunner

Chapter

screens/CanyonRunner

Environmental

screens/CanyonRunner

Mobile

screens/CanyonRunner

screens/CanyonRunner

Title

screens/CanyonRunner

Two

Different

## I Open Sourced My Game

Building CanyonRunner was a tremendous amount of fun, thanks largely to Richard Davey's excellent [Phaser framework](http://phaser.io/). Along the way, I was assisted by many helpful Phaser community members and developers, so I wanted to give back by:

1. [Open sourcing my game (check out the repo here)](https://github.com/zackproser/CanyonRunner) 2. Offering code samples and explaining some of the features I implemented

I built this game from start to finish in 76 days. In the course of developing it, one of the running themes I noticed on the Phaser forums was that most developers were tackling their first game and were unsure about how to implement common game features like saved games, multiple levels, different experiences for mobile and desktop, etc.

Phaser is well organized and documented, so while its various API's and systems were easy to get started with, it was less clear to many developers how to fit everything together into a coherent gaming experience. [I open sourced CanyonRunner](https://www.github.com/zackproser/canyonrunner) and decided to do an in-depth post about its various features in order to create a resource for other developers that might be in the middle of developing their own HTML5 game.

Hopefully some of the features I built into CanyonRunner, such as player-specific saved games, multiple levels each with their own atmosphere and game mechanics, different experiences optimized for desktop / mobile, and alternate endings determined by player performance, will resonate with and assist other game developers.

To get a sense of CanyonRunner, or to play it through in its entirety (which will take you less than 10 minutes if you make zero mistakes), [click here to play! ](https://canyonrunner.pages.dev)

## Screenshots

Here's a look at some screenshots from the actual game. I wanted the game to have a retro feel. At the same time, the story, presented via inter-level navigation sequences, builds up an eerie atmosphere.

**_Intense Aerial Dogfights. Fight Off Marauding Bandits_**.

**_Wind Your Way Through the Eerie Story_**

_Intense Environmental Effects_**

**_Auto-detects Mobile agents and renders a touchpad_**

**_Catchy Music Keeps The Pace Throughout The Game_**

**_Save System Keeps Your Score & Progress_**

**_Multiple Endings For Higher Replay Value_**

## Playthrough Video

Want to get a sense of the gameplay without dodging spires yourself? Watch this full playthrough of the game to quickly get up to speed on the feel and main game mechanics of CanyonRunner.

## Table of Contents

What you'll find in this post:

Game overview

Project structure

Using Node.js as a simple server

Creating a preloader

Creating a splash screen

Linking separate levels

Creating a save system

Creating different experiences for desktop and mobile

Creating multiple endings

Settings buttons

pause & mute

Game overview

CanyonRunner is a 2D side-scrolling action & adventure game, complete with a story, two possible endings, automatically saved game progress, aerial dogfights and air to air missle combat, and atmospheric special effects.

You assume the role of the mysterious CanyonRunner, a lone pilot navigating their rocket through a perilous 3 stage journey as they struggle to return to their family with desperately needed supplies.

Depending upon their performance, players are shown one of two possible endings after completing the game.

Project Structure

The CanyonRunner project is structured such that:

1. The development workflow is easy to understand and organized 2. Building a packaged and optimized distribution of the game can be done in one step

You can [view the full project on Github here](https://github.com/zackproser/CanyonRunner) if you want to explore the structure on your own.

Let's take a look at the project's file tree, then consider the general purpose of each directory in turn:

Project root

**.gitignore:** This special file tells the version control system, git, which files it can "ignore" or not worry about placing under source control. If your game project is generating logs, debug output, or uses node_modules, you can save space in your repository by specifying these files and directories in your .gitignore file.

**Gruntfile.js:** I used the command line task-runner [Grunt](http://gruntjs.com/) in order to automate some of tedious and repetitive development tasks. Grunt will be familiar to many web developers, but for those of you who have not encountered it before, Grunt allows you to define tasks, namely those that you find yourself repeatedly having to perform while developing, and bundle them together into a single or a few commands.

As an example, if you are working with scss, you may constantly find yourself performing the same mundane tasks as you build out your project, such as concatenting 4 different scss files together, then compiling them to raw css, then minifying that resulting css file and moving it into a specific folder where it can be served.

Instead of doing this manually each time, you can configure a grunt task to do exactly these steps in that order - and all you'd have to do is type "grunt" on the command line. Better yet, Grunt can "watch" certain files or directories for changes and then perform associated actions on its own. You can even set up completely customized tasks to perform, as we'll see in a moment with the Google Closure Compiler for optimizing JavaScript.

Grunt can be painful to set up and configure, and often times it's overkill for a small project, but it can effectively streamline your workflow if you're dealing with multiple source files, concatenation and minification. In CanyonRunner, as in many Phaser projects, I save off each game state as a separate javascript file for sanity while developing, but we only want to serve as few minified javascript files as possible with our finished game. This makes Grunt a logical choice.

**compiler.jar:** This is the [Google Closure Compiler](https://developers.google.com/closure/compiler/), which is a tool that makes JavaScript download and run faster. After concatenating all my custom JavaScript into a single file, I run it through the Closure Compiler so that the final output .js file that is served up by the game is as lean and mean as possible. The compile command within the exec task uses the compiler.jar to generate the optimized CanyonRunner.min.js, save it to the correct build directory, and echo a string confirming the task completely successfully:

Note that the assets are also moved by Grunt into their correct destinations during the build. Once your Gruntfile is in place and configured correctly, it's much easier to just type "grunt" in your terminal and get a perfectly built game as an output than to build one manually. This is doubly true if you're testing something that requires you to make changes and then build, or if you're trying to remember how to build the project after 3 months of not touching it.

#### Creating a Distribution-Ready Build in One Command

The reason we soldier through the initial tedium of configuring Grunt is that once we have everything set up, we can literally build a distribution-ready copy of our game in a single command:

If you've cloned [the CanyonRunner repo](https://github.com/zackproser/CanyonRunner) and are following along, you can cd into the project root and type grunt in your terminal to see what I'm talking about. Grunt will execute the tasks configured in your Gruntfile to concatenate all the javascript files, run them through the Google Closure Compiler, copy all the assets correctly and put everything where it belongs: into a single new directory called CanyonRunner-distribution which will appear in the project root.

This distribution directory can now be named whatever you want and handed off to a customer or game hosting site for distribution. Having this build infrastructure in place will save you dozens of hours over the course of a project.

### Directories at a Glance

Now, let's consider the purpose of each directory.

`assets`. This directory holds the audio files, background images, and spritesheets used by our game.

`build`. This directory is home to the files that are required by our game to work, such as the actual Phaser framework files. Our build tool gathers up only what is needed from here when creating a distribution.

`css`. Holds the simple stylesheet required to make the orientation (rotate your phone!) image work properly.

`icons`. Holds the various sized app icons that would be required by, say, an iOS app that was loading your Phaser game in a webview.

`images`. This directory holds a special image required by Phaser to render the screen telling the user they should rotate their phone to landscape mode in order to play the game. When phaser detects that the user's phone is being held upright, this image is used to render that hint on screen.

`node_modules`. This is the directory where npm, node's package manager, installs dependencies. When you **require** a module in your node.js script, one of the places node looks for that module is here. In the case of this project, our server.js file (see next section) uses the express module, which would end up here after running the npm install command.

`src`. Arguably the most important directory, this holds all of our separate .js files - for each level, menu, interstitial scene, etc. Once we have finished our game and we're ready to make a build, our build tool will look into this directory to gather together all the separate files into one single concatenated and minified javascript file that's fit for distribution with our finished game.

Running a local fileserver to ease development with node.js

While developing a Phaser game it is very helpful to have a local fileserver that we can run with a single command. This makes it easy to serve up our index.html file locally, which loads all our Javascript and Phaser files so we can see and play our game as we're building it.

You could set up a traditional web stack with apache, or use something that handles this for you such as [Mamp](https://www.mamp.info/en/). I feel these options are too involved for what we want to do: simply serve up our index.html file locally so we can view it at localhost:8080\. Our index.html file will in turn load the Phaser library, and then our game itself so we can test changes with low hassle and iterate quickly.

[Follow these instructions to install Node.js on your machine](https://nodejs.org/download/). Once that's done, you can run the server.js file in the project root by typing:

Now you can play and test your Phaser game by typing localhost:8080 into your browser.

Let's take a look at what this simple utility script looks like:

**Notice we're requiring the [Express](http://expressjs.com/) module** to abstract away serving static assets. This means you'll need to install express library locally to your project in order for server.js to work. If you don't already have express installed globally on your system, type:

This command will pull down all required dependencies from npm, node's package management system.

With our simple fileserver in place, all we have to do to view changes to our source code or playtest our game is run our server and visit localhost:8080.

Creating a preloader

Phaser games use preloaders as special game states to perform setup and configuration tasks that can or must be run before a more interactive game state is loaded.

Let's examine CanyonRunner's preloader state. It has a few important setup tasks to perform. First, it sets up the background for the splashscreen and loads the various levels' background images.

There's a very handy Phaser convenience feature known as a Preload Sprite that I'm taking advantage of here to render the loading bar that says "loading" and expands from 0 to 100% as the splashscreen itself is being prepared. First, you set up the sprite that will be used as the preloadBar. Then you can call the method setPreloadBar and pass in the sprite - Phaser handles all the internal timing and display logic for us.

#### Optimizing Asset Delivery with Audiosprites

Another important task that falls to the preloader is to determine which type of audiosprite should be loaded. An audiosprite is a single file that contains all the sound effects and songs required by the game mashed together, to save space and bandwidth during asset delivery. Tools that create audiosprites also export a map file, usually in json or xml, that explicitly states the time slices of the audiosprite wherein each individual sound and song starts and ends.

Let's take a look:  This mapping file allows frameworks like Phaser to efficiently deliver a single audio file, while still allowing the game developer the convenience of referring to individual sounds and songs by their key names while programming.

After developing CanyonRunner by using separate audio files for each sound, I used a free tool to create one single audiosprite for each supported filetype (different browsers support different audiosprite filetypes). That's why the preloader uses Phaser's device helper methods to determine which browser the player is using. Firefox and Chrome support .ogg files, while Safari supports .m4a. You can convert your final audiosprite to both formats and include it in your assets directory. With your preloader determining the proper format based on your user's browser, each player will get a single optimized audiosprite that will run perfectly for them.

Creating a splash screen

Successfully building a complete game requires attention to lots of small details which, taken together, build up a feeling of a polished and finished product.

One of the first contact points our players will have with our game is the splashscreen. A good splashscreen can set up the feel and mood of the game, begin introducing the themes that will run throughout, and get the player excited about playing. Let's take a look at how we can create a splashscreen for our Phaser HTML5 game.

Here is the full MainMenu.js file for CanyonRunner, which sets up the intro splashscreen and waits for the player to click the Start button:  From reading through the source code you can see that the MainMenu.js file does a few key things:

Checks if the current player has saved game data, and renders it

Creates the background, starts scrolling the screen, adds the rocket

Plays the intro music

Runs an initial "flying scene" with afterburners and a subtle speed

up

Sets up a startGame function, bound to the start button

A good splashscreen could be as simple as a static background image with a start button. The main goal is to provide an introduction to the game. Notice I've created a mute button on the intro scene - but not a pause button. It's a good idea to give your player the option to mute the game early on in case they're playing in a situation where they don't want sound output. However, on this particular screen a pause button is irrelevant, since the intro scene will loop forever until the user taps or clicks Start.

### Creating and Linking Separate Levels

At a high level, the process of creating and linking together separate levels involves writing a separate .js file representing each level and placing it in the src/ directory. I'd strongly suggest naming each file after the scene or level it renders. This way you can always quickly and easily find the file you need to edit when you want to make a change to a particular level. Once we're code complete on our game, our build tool will gather up all these separate .js files and concatenate them into a single file that represents our Phaser game.

#### Making Levels Unique with New Game Mechanics

In the case of CanyonRunner, I looked at each level as an opportunity to explore a new game mechanic. Level 1 is the most straightfoward level and the easiest level to complete. This is because I'm conscientiously using it to introduce players to the first main game mechanic that will be carried throughout the game: avoiding craggy and somewhat randomized spires.

While Level 1 is not the most exciting or challenging level, it is a necessary introduction to a few key facts that a successful player of CanyonRunner must understand:

Spires are coming at you constantly, in varying and sometimes unpredictable positions

Smashing into a spire causes you damage

Your rocket can take up to 3 hits before exploding

You can "heal" your rocket by catching medkits (yes, medkits

it's a cartoony game)

When backed into a corner, you can blast spires with your missiles

Your missiles are unlimited, but take a long time to reload, so you have to fire carefully

When you break out the key lessons like this, it becomes clear that there's actually a good deal going on in the first level. Meanwhile, these are concepts that are true for every subsequent level in CanyonRunner, so it's important to get clear on them upfront.

Level 2 is my personal favorite and it introduces a completely new game mechanic: dogfights. In level 2, you are hounded by a series of bandits armed with missiles as powerful as your own. Taking a hit from one of these enemy missiles means instant death and restarting the level. There's an entire mini-game to figuring out how the bandits behave and track you, and what you have to do in order to actually hit them with a missile.

At the same time you're dodging enemy missiles and trying to shoot down bandits, those spires and healthkits are still coming at you in somewhat randomized fashion. Thus, the gameplay here is compounding and builds upon previous mechanics. At times, you may be forced by an enemy missile to take a direct hit from a spire, since you know your rocket can withstand up to three of those, but will be instantly destroyed by an enemy missile.

Level 3 likewise introduces its own unique mechanic: a vicious meteor storm. While continuing to dodge spires and collect healthkits that are coming at you horizontally, you must also successfully dodge somewhat randomized and potentially very fast meteoroids that are raining down on you vertically. As is true with the spires, your rocket can survive up to three direct hits from a meteoroid. The now two planes of deadly obstacles compound on one another to create the most movement-intensive level in CanyonRunner.

Incrementally introducing new game mechanics in this manner is a good way to increasingly challenge your players as your game progresses, while still making the gameplay feel logical and sensible. Throwing all of these mechanics at the player from the get-go could result in the game feeling overly complex and runs the risk of alienating and overwhelming the player, who is then unlikely to return. It's far better to allow the player to make some degree of progress while learning the core mechanics before throwing your biggest challenges at them.

Handling transitions between levels

Let's take a quick look at how level1.js handles the next 3 possible states following play:

The user succeeds

and should be passed to the next scene

The user fails

and should be passed to the main menu after we preserve their game data

The user quits

and should be passed directly to the main menu

Here are the functions at the end of level1.js that handle these transitions. While the crux of switching states is calling Phaser's state.start method, you'll usually want to perform certain tear-down or data persistance tasks before making the switch:

In the case of the player failing or succeeding on the given level, their latest score, their furthest position in the game (as stored in the returnPlayerToState attribute) and their current in-game time are stored via the game's save system before the player is advanced to the next state. See the next section for a complete treatment of a Local Storage based game-save system.

Creating a game save system

HTML5 features a robust storage system known as [Local Storage. ](http://www.html5rocks.com/en/features/storage) Local storage offers an attractive means of persisting user data for HTML5 game developers. It is widely supported across many different browser and devices and offers a simple interface for storing and retrieving custom objects.

In the case of CanyonRunner, I store a few key things on the user's system so that I can persist their game progress in case they complete only one or two levels in one session and return later. I call this object playerStats - it's a json object with 3 attributes:

The user's Top Score (represented by the number of spires they've avoided)

The user's current time

in

game represented by the number of seconds they've survived

The name of the game state that the user should be returned to (updated as they progress through the game)

 The create function of a given Phaser state is the perfect time to inspect localStorage to see if the player already has an object stored (and to create one if they don't).

Invoking the Local Storage API, I use the localStore.getItem method to check for the special object name I use to set save objects for CanyonRunner. The idea here is similar to namespacing your WordPress plugins - you don't have control over the storage keynames that other developers might write to the user's browser via other games, webapps or websites. To prevent collisions, you should namespace your storage object's name to your game - adding some random numbers decreases the chances of collision.

In the previous gist above, you can see the logic for updating the player's progress and scores in the handlUserDataLoss and handleUserDataLevelComplete functions.

Creating different experiences for desktop and mobile devices

This is probably my personal favorite feature of CanyonRunner. Let's say I have CanyonRunner set up and hosted at a particular URL. If you visit this URL with your desktop / laptop browser, you'll get the full desktop version - complete with the keyboard control scheme and the extra fancy (and resource intensive!) particle effects like rocket and missile afterburners and glowing healing mist on healthkits.

However, should you happen to hit the same URL with your smartphone, you'll be given the optimized mobile version, with touchpad controls rendered right over the game scene, and no particle effects (to drastically improve mobile performance).

I implemented this feature because I wanted one single instance of the CanyonRunner game to work for all players regardless of what device they were using to play. As the game developer, this also makes my life easier, because once I have the logic and assets in place to handle and serve the two different versions of the game, I don't have to worry about supporting and keeping on parity two actually separate codebases.

The two main pieces to this feature are the game logic that checks for whether the player is using a desktop or mobile device, and the assets and functions that work together to render the mobile touchpad on screen and bind its buttons to the correct player actions. Let's take a look:

 You can see I'm leveraging Phaser's game.device.desktop method to determine which type of device the player is using, allowing me to implement the two control schemes within an if else statement. Notice that when rendering the mobile gamepad, I'm setting each button's fixedToCamera property to true.

Given that CanyonRunner is a side-scroller, doing this prevents the buttons from sliding off the screen at the start of the level, which would make them considerably less useful to the player.

Phaser's helper device methods that determine which kind of device your players are using make it easy to optimize your game experience for desktop, mobile and tablet form-factors simultaneously.

Creating multiple endings depending upon player performance

Recent triple A titles as well as classic old school games have explored the concept of multiple endings. Multiple endings increase replay value by allowing players to do multiple playthroughs, following different paths or making different major plot decisions depending on the type of ending they are trying to get.

Multiple endings also allow you to make thematic statements about the kinds of choices, behaviors or chance occurrences that lead to your protagonist achieving either glory or infamy, salvation or condemnation.

I wanted to explore this concept with CanyonRunner, so I implemented a simple multiple ending system. You will get one of two possible endings when you play through CanyonRunner, depending upon how quickly you complete the game. This is one of the reasons that I keep track of the player's "Top Time" or total number of seconds since beginning to play through Level 1\. This concept of time being precious and limited is thematically harmonious with CanyonRunner's story: you are racing desperately needed food and supplies home to your family in a barren post-apocalyptic wasteland. If you take too long doing so, you simply no longer have a family to return to.

Creating multiple endings

If you want to implement multiple endings in your own Phaser game, the underlying logic of how you determine which ending a player unlocks is up to you, but here's a high level overview of how you would organize such a concept in your code:

As the player progresses through your game, you keep tabs on one or more performance metrics. This could be their total score, how many hostages they rescued, what physical percentage of the world they explored and walked over, how much gold they ended up with, how many innocents they waxed, etc. If you want this to persist between game sessions, you'll want to store this information either via Local Storage, a cookie, or your user database if you have one.

After the player has completed the final level, or slain the final boss, or found the final hidden object, at whichever point in your particular game the player is considered to have "won", you can have some logic that inspects this player performance information to make a determination about which game state they will proceed to.

Maybe your player collected over 1500 gold throughout the course of playing, and rescued 25 innocents, so they'll receive the "You are rich and beneficent and live happily ever after" ending. Maybe they killed every NPC they came across to enrich themselves, so they'll get the "You're an infamous monster that nobody likes" ending.

At this point, actually showing the player the correct ending is simply a matter of calling game.state.start with the right state name for the ending they've earned.

#### Creating the Ending

Determining Game Logic

Let's take a look at how I implemented this in CanyonRunner. Regardless of which ending the player will ultimately unlock, all players will see this interstitial scene after completing the 3rd level. It's the scene that shows the CanyonRunner obtaining a lock on their home beacon and descending to land at home. This makes it a great place to execute the logic that determines which ending to give the player, since this is something that can be done in the background while the player is watching the actual scene on screen. You can see where I'm determining and starting the correct ending within the rocketLanding function:

javascript ///////////////////////////// //CREATE SOUND TOGGLE BUTTON ///////////////////////////// this.soundButton = this.game.add.button(this.game.world.centerX + 240, this.game.world.centerY - 290, 'sprites', this.toggleMute, this, 'sound-icon', 'sound-icon', 'sound-icon'); this.soundButton.fixedToCamera = true; if (!this.game.sound.mute) { this.soundButton.tint = 16777215; } else { this.soundButton.tint = 16711680; } ////////////////////// //CREATE PAUSE BUTTON ////////////////////// this.pauseButton = this.game.add.sprite(this.game.world.centerX + 320, this.game.world.centerY - 280, 'sprites', 'pause-button'); this.pauseButton.inputEnabled = true; this.pauseButton.fixedToCamera = true; this.pauseButton.events.onInputUp.add(function() { this.game.paused = true; this.pauseButton.tint = 16711680; }, this); this.game.input.onDown.add(function() { if (this.game.paused) this.game.paused = false; this.pauseButton.tint = 16777215; }, this);

...

toggleMute: function() { if (!this.game.sound.mute) { this.game.sound.mute = true; this.soundButton.tint = 16711680; } else { this.game.sound.mute = false; this.soundButton.tint = 16777215; } }, ``` As with our mobile touchpad buttons, it's important to set the fixedToCamera attribute to true on any icon or button we want to remain in place in our side-scroller game.

Notice that I conditionally tint the pause and mute buttons depending upon their status - this is an easy way to make the buttons and the entire game interface feel more responsive, as well as to provide a necessary visual signal to the player about whether or not the game is currently paused or muted.

As you can see in the code, Phaser is doing all the heavy lifting for us when it comes to actually pausing game execution or muting sound. As developers, we need only flip the boolean property of game.sound.mute or game.paused as makes sense within our interface logic, and the framework handles it from there.

### That's All for Now

I hope this tutorial and examination of some of CanyonRunner's game mechanics and features was helpful to you. If it was, please say thanks by sharing this post or starring [the CanyonRunner repo on Github](https://github.com/zackproser/CanyonRunner).

If something isn't clear or if you'd like to see some other feature or mechanic explained that isn't called out here, or if you just have general feedback, please [drop me an e-mail. ](mailto:zackproser@gmail.com)

## What is opengraph?

Opengraph is a standard for social media image formats. It's the "card" that is rendered whenever you or someone else shares a URL to your site on social media:

It's considered a good idea to have an opengraph image associated with each of your posts because it's a bit of nice eye candy that theoretically helps improve your click-through rate. A high quality opengraph image can help make your site look more professional.

## Implementing my desired functionality

This took me a bit to wrap my head around. The [examples Vercel provides](https://vercel.com/docs/functions/edge-functions/og-image-generation/og-image-examples) were helpful and high quality as usual, (they even have a helpful [opengraph playground](https://og-playground.vercel.app/)) but I wish there had been more of them. It took me a while to figure out how to implement the exact workflow I wanted:

1. I add a "hero" image to each of my posts which renders on my [blog's index page](/blog). I wanted my opengraph image for a post to contain the post's title as well as its hero image 2. I wanted a fallback image to render for my home or index pages - and in case the individual post's image couldn't be rendered for whatever reason

In this way, I could have an attractive opengraph image for each post shared online, while having a sane default image that does a good job of promoting my site in case of any issues.

In general, I'm pretty happy with how the final result turned out, but knowing myself I'll likely have additional tweaks to make in the future to improve it further.

If you look closely (right click the image and open it in a new tab), you can see that my image has two linear gradients, one for the green background which transitions between greens from top to bottom, and one for blue which transitions left to right.

In addition, each band has a semi-transparent background image - giving a brushed aluminum effect to the top and bottom green bands and a striped paper effect to the center blue card where the title and hero image are rendered. I was able to pull this off due to the fact that Vercel's '@vercel/og' package allows you to use Tailwind CSS in combination with inline styles.

## Per-post images plus a fallback image for home and index pages

This is my fallback image, and it is being rendered by hitting the local `/api/og` endpoint. It's `src` parameter is `${process.env.NEXT_PUBLIC_SITE_URL/api/og}` which computes to "${process.env.NEXT_PUBLIC_SITE_URL}/api/og".

Example dynamically rendered opengraph images for posts:

**Blog post with dynamic title and hero image** 

**Another blog post with dynamic title and hero image** 

**Blog post with dynamic title but fallback image**

Having gone through this exercise, I would highly recommend implementing a fallback image that renders in two cases: 1. If the page or post shared did not have a hero image associated with it (because it's your home page, for example) 2. Some error was encountered in rendering the hero image

Here's an example opengraph image where the title was rendered dynamically, but the fallback image was used:

## Understanding the flow of Vercel's '@vercel/og' package and Next.js

This is a flowchart of how the sequence works:

In essence, you're creating an API route in your Next.js site that can read two query parameters from requests it receives: 1. The title of the post to generate an image for 2. The hero image to include in the dynamic image

and use these values to render the final `@vercel/og ImageResponse`. Honestly, it was a huge pain in the ass to get it all working the way I wanted, but it would be far worse without this library and Next.js integration.

In exchange for the semi-tedious experience of building out your custom OG image you get tremendous flexibility within certain hard limitations, which you can read about here.

Here's my current `/api/og` route code, which still needs to be improved and cleaned up, but I'm sharing it in case it helps anyone else trying to figure out this exact same flow.

This entire site is open-source and available at [github.com/zackproser/portfolio](https://github.com/zackproser/portfolio)

Here's my `ArticleLayout.jsx` component, which forms the `` in the head of each post to provide the URL that social media sites will call when rendering their cards:

## Thanks for reading

If you enjoyed this post or found it helpful in anyway, do me a favor and share the URL somewhere on social media so that you can see my opengraph image in action 🙌😁.

---

I built Pinecone's first AWS Reference Architecture using Pulumi.

This is the seventh article I wrote while working at Pinecone:

Read the article

image

create

create

404

stable

replicate

stable

rest

create

ai

 You can generate images using AI for free online through a variety of methods

I've been generating images using models such as StableDiffusion and DALLE for blog posts for months now. I can quickly produce high-quality images that help tell my story.

This blog post will give you a lay of the land in what's currently possible, and point you to some resources for generating AI images whether you are as technical as a developer or not - and whether you'd prefer to produce images via a simple UI or programmatically via an API.

In addition, I'll give you the minimum you need to understand about prompts and negative prompts and how to use them effectively.

## DALLE

3 via Bing Create

Overall, this is probably the best option right now if you want high quality images without needing to pay.

You will need a Microsoft live account (which is free), but otherwise you just log into [bing.com/create](https://bing.com/create) and you write your prompt directly into the text input at the top:

This is using OpenAI's DALLE-3 model under the hood, which is noticeably better at converting the specific details and instructions in natural human language into an image that resembles what the user intended.

I have been generally pretty impressed with its outputs, using them for recent blog post hero images as well as my own [404 page](/this-page-doesnt-exist).

For example, I used Bing and DALLE-3 to generate the hero image for this post in particular via the following prompt:

> Neon punk style. A close up of a hand holding a box and waving a magic wand over it. From the box, many different polaroid photos of different pixel art scenes are flying upward and outward.

Bing currently gives you 25 "boosts" per day, which appears to mean 25 priority image generation requests. After you use them up, your requests might slow down as they fall toward the back of the queue.

Using DALLE-3 in this way also supports specifying the style of art you want generated upfront, such as "Pixel art style. Clowns juggling in a park".

## Discord bots

Discord is the easiest and lowest friction way to get started generating images via StableDiffusion right now, especially if you're unwilling to pay for anything.

Stable Foundation is a popular Discord channel that hosts several different instances of the bot that you can ask for image generations via chat prompts.

Here's [the direct link to the Stable Foundation discord invite page](https://discord.com/invite/stablediffusion)

This is a very handy tool if you don't need a ton of images or just want the occasional AI generated image with a minimum of setup or fuss required. You can run discord in your browser, which makes things even simpler as it requires no downloads.

## There's some important gotchas to generating AI images via Discord services

There are important catches to be aware of: the good folks behind the Discord channel and the bots that generate your images are paying for the GPUs and compute required to handle their large user base's many requests, so after you've generated a couple of images, you'll eventually ask for another and be told to chill-out for a bit.

This is their Discord channel's way of rate-limiting you so that you don't cost them too much money and overwhelm the service so that other users can't generate images.

And this is fair enough - they're providing you with free image generation services, after all. Like other free online services, they also will not allow you to generate content that is considered not safe for work, or adult.

Also fair enough - it's their house their rules, but occasionally you'll run into slight bugs with the NSFW content detector that will incorrectly flag your innocent image prompt as resulting in NSFW content even when you didn't want it to, which can lead to failed generations and more wasted time. If you want total control over your generations, you need to go local and use a tool like AUTOMATIC111, mentioned below.

Finally, because it's a Discord channel that anyone can join, when you ask the bot for your images and the bot eventually returns them, everyone else in the channel can see your requests and your generated images and could download them if they wanted to.

If you are working on a top-secret project or you just don't want other people knowing what you're up to, you'll want to look into AUTOMATIC111 or other options for running image generation models locally.

## Replicate.com

[Replicate](https://replicate.com) is an outstanding resource for technical and non-technical folks alike. It's one of my favorite options and I use both their UI for quick image generations when I'm writing content, and I use their REST API in [my Panthalia project](https://github.com/zackproser/panthalia) which allows me to start blog posts by talking into my phone and request images via StableDiffusion XL.

Replicate.com hosts popular machine learning and AI models and makes them available through a simple UI that you can click around in and type image requests into, as well as a REST API for developers to integrate into their applications.

Replicate.com is one of those "totally obvious in retrospect" ideas: with the explosion of useful machine learning models, providing a uniform interface to running those models easily was pretty brilliant.

To use Replicate go to replicate.com and click the Explore button to see all the models you can use. You'll find more than just image generation models, but for the sake of this tutorial, look for StableDiffusionXL.

Once you're on the StableDiffusionXL model page, you can enter the prompt for the image you want to generate. Here's an example of a simple prompt that works well:

> Pixel art style. Large aquarium full of colorful fish, algae and aquarium decorations. Toy rocks.

If you're a developer and you don't feel like wrangling Python models into microservices or figuring out how to properly Dockerize StableDiffusion, you can take advantage of Replicate's REST API, which is truly a delight, from experience:

I have generated a ton of images via Replicate every month for the past several months and the most they've charged me is $2 and some change. Highly recommended.

## AUTOMATIC111

[This open-source option](https://github.com/AUTOMATIC1111/stable-diffusion-webui) requires that you be comfortable with GitHub and git at a minimum, but it's very powerful because it allows you to run StableDiffusion, as well as checkpoint models based on StableDiffusion, completely locally.

As in, once you have this up and running locally using the provided script, you visit the UI on localhost and you can then pull your ethernet cord out of your laptop, turn off your WiFi card's radio and still generate images via natural language prompts locally.

There are plenty of reasons why you might want to general images completely locally without sending data off your machine which we won't get into right now.

AUTOMATIC111 is an open-source project which means that it's going to have some bugs, but there's also a community of users who are actively engaging with the project, developers who are fixing those bugs regularly, and plenty of GitHub issues and discussions where you can find fellow users posting workarounds and fixes for common problems.

The other major benefit of using this tool is that it's completely free. If your use case is either tricky to capture the perfect image for, or if it necessitates you generating tons of images over and over again, it may be worth the time investment to get this running locally and learn how to use it.

AUTOMATIC111 is also powerful because it allows you to use LoRa and LyCORIS models to essentially fine-tune whichever base model you're using to further customize your final image outputs.

LoRA, short for Low-Rank Adaptation, models are smaller versions of Stable Diffusion models designed to apply minor alterations to standard checkpoint models. For example, there might be a LoRa model for Pikachu, making it easier to generate scenes where Pikachu is performing certain actions.

The acronym LyCORIS stands for "Lora beYond COnventional methods, Other Rank adaptation Implementations for Stable diffusion." Unlike LoRA models, LyCORIS encompasses a variety of fine-tuning methods. It's a project dedicated to exploring diverse ways of parameter-efficient fine-tuning on Stable Diffusion via different algorithm implementations.

If you want to go deeper into understanding the current state of AI image generation via natural language, as well as checkpoint models, LoRa and LyCORIS models and similar techniques for getting specific outputs, AUTOMATIC111 is the way to go.

If you are working with AUTOMATIC111, one of the more popular websites for finding checkpoint, LoRa and LyCORIS models is civit.ai which hosts a vast array of both SFW and NSFW models contributed by the community.

## Prompt basics

Prompting is how you ask the AI model for an image in natural human language, like "Pixel art style. Aquarium full of colorful fish, plants and aquarium decorations".

Notice in the above examples that I tend to start by describing the style of art that I want at the beginning of the prompt, such as "Pixel art style" or "Neon punk style".

Some folks use a specific artist or photographer's name if they want the resulting image to mirror that style, which will work if the model has been trained on that artist.

Sometimes, results you'll get back from a given prompt are pretty close to what you want, but for one reason or another the image(s) will be slightly off.

You can actually re-run generation with the same prompt and you'll get back slightly different images each time due to the random value inputs that are added by default on each run.

Sometimes, it's better to modify your prompt and try to describe the same scene or situation in simpler terms.

## Adding emphasis in StableDiffusion image generation prompts

For StableDiffusion and StableDiffusionXL models in particular, there's a trick you can use when writing out your prompt to indicate that a particular phrase or feature is more important and should be given more "weight" during image generation.

Adding parends around a word or phrase increases its weight relative to other phrases in your prompt, such as:

> Pixel art style. A ninja running across rooftops ((carrying a samurai sword)).

You can use this trick in both StableDiffusion and StableDiffusionXL models, and you can use (one), ((two)) or (((three))) levels of parends, according to my testing, to signify that something is more important.

## Negative prompts

The negative prompt is your opportunity to "steer" the model away from certain features or characteristics you're getting in your generated images that you don't want.

If your prompt is generating images close to what you want, but you keep getting darkly lit scenes or extra hands or limbs, sometimes adding phrases like "dark", "dimly lit", "extra limbs" or "bad anatomy" can help.

## Why generate images with AI?

> Neon punk style. An android artist wearing a french beret, sitting in the greek thinker position, and staring at a half-finished canvas of an oil painting landscape. In a french loft apartment with an open window revealing a beautiful cityscape.

My primary motivation for generating images with AI is that I write a ton of blog posts both in my free time and as part of [my day job](/blog/i-am-joining-pinecone-io), and I want high-quality eye candy to help attract readers to click and to keep them engaged with my content for longer.

I also find it to be an absolute blast to generate Neon Punk and Pixel art images to represent even complex scenarios I'm writing about - so it increases my overall enjoyment of the creative process itself.

I have visual arts skills and I used to make assets for my own posts or applications with Photoshop or Adobe Illustrator - but using natural language to describe what I want is about a thousand times faster and certainly less involved.

I've gotten negative comments on Hacker News before (I know, it sounds unlikely, but hear me out) over my use of AI-generated images in my blog posts, but in fairness to those commenters who didn't feel the need to use their real names in their handles, they woke up and started their day with a large warm bowl of Haterade.

I believe that the content I produce is more interesting overall because it features pretty images that help to tell my overall story.

functions

integrated

js

Ten days ago, I wrote about how maintaining the last generation of my personal portfolio site absolutely FUCKING SUCKED, and how I knowingly did things the painful way on purpose for years.

And yet, here I am *exactly ten days later*, writing that maintaining this site *no longer fucking sucks*. What has changed in so short a time between that post and now?

***

## I tried Vercel, and the latest Next.js

In this post, I'll share what I learned during my most recent total site re-write. I may have just fallen in love.

## The latest stack

Next.js 13.3

Tailwind UI / CSS

Vercel for framework defined infrastructure

### Even as a grumpy developer,  I was pleasantly surprised. This stuff is *fast* Why? Error reporting is light-years better than I ever remember it being for frontend development.

The errors were human legible and I experienced very few WTF moments throughout a weekend of furious hacking. This is some of the highest ratio of time spent just building features and shipping content versus wrestling with my tooling and debugging why something simple isn't working.

The tooling is fast now. The hot reloading works more smoothly. I can now run `npm run dev` once and hack away. The bundle compiling is noticeably quicker.

## Things that used to be tedious are not now

It feels like I stepped away for a couple years and then many of the UX pain points for developing on the frontend were handily resolved. To give some concrete examples, the way next.js handles loading Google fonts.

In the far distant past, a couple of years ago, loading a Google Font on your site used to involve a bunch of manual tedium. You had to peruse the Google fonts site, figure out what looked okay, download the fonts, unzip them, install them properly, realize you didn't install them properly and try again, and, of course, you had to spend the requisite 2 hours on stack overflow figuring out why none of them would load.

Here's how you use Google Fonts now in a next.js project, by writing out its name in a component:

More importantly, the optimization happens server-side - and a request for the font is *never sent to Google* when someone hits your site. This is fantastic. The other thing I forgot to mention about using Google Fonts in the past is that you also had to do it incorrectly so that you could slow your page way down. With next.js, it's now painless to create a branch with a font experiment. Painless, I tell you! You had to be there in the past back when it was bad to understand why this is amazing.

It took me three minutes to throw up a branch with a pull request to review what it would look like to change my site back to the custom Google Font Oxygen I've used in the past.

I was able to pop open two firefox tiled windows, one with production and one with my font experiment, and eyeball the two fonts together side by side and decide I wanted to stick with the default.

Again, the speed of iteration is profound. It enables new ways of working. In the past you had to know me personally to be able to file a ticket to ask me to screw with the fonts on the site. You had to bring oblations and say you were sorry. Now you can preview whatever font you want, buddy!

### Enhanced Markdown or MDX works intuitively

Most of the things I was doing via libraries and custom code in the previous generation of my site to get the markdown authoring experience I wanted, are now first-class features of MDX / next.js.

This includes the use of the prism library for code syntax highlighting which even supports custom themes (the one you're seeing on my code snippets is `gruvbox` and it's the same one I use for in my Neovim setup).

So far, the MDX integration has been exactly how I wanted to work with my blog content, because markdown is easy to maintain and quick to write, but I can now also render rich graphs or media to enrich my content.

Here's a quick snippet to show what I mean. For the most part, it's still just markdown, but now you can build and import rich graphs and diagrams, images, gifs (which I use for terminal demos on my site), and much more:

I can think of a number of projects over the past years this would have significantly simplified. Years, ago, we tediously composed rich-media "threat reports" as giant web pages that required constant collaboration with multiple departments. This stack is ideal for such contexts.

I had the majority of my site's content migrated from my previous site to my Tailwind UI template site in a single weekend. The integration with tailwind was seamless and the integration with Vercel was seamless.

Once I had tweaked the template to my needs, I was mostly moving markdown in place, rewriting image links and adding some MDX / react preamble to my markdown files, which otherwise did not need to change. This was one of the least painful migrations I've done. Full stop.

### React is pretty fast, as is - everything else...

The git integration with Vercel is excellent just as it is with Netlify. It allows for very rapid iterate, preview, deploy cycles. I built a working landing page with a form component (meaning there's a backend dependency as well!) by composing some components I had just modified and gotten working, deployed it to a preview environment, verified it there, and promoted it to production within 13 minutes - *while having a conversation with someone*.

This is a testament to this tooling and all the work that has gone into making the developer experience excellent. At long last, I may also have settled on a slight personal preference for React. Something about it clicked enough where it felt natural to create, and easy to reuse, my UI components.

### The backend API handling is really nice once you grok it

Again, speaking from the perspective of speed of iteration and rapid deployment, the next.js API integration / backend function generation is actually really nice. Imagine you have a form that needs to capture emails from your blog subscribers and send it on to a third party service to subscribe the new reader to your list.

Next.js uses the concept of Pages, which are React components exported from [`.js`, `.jsx`, `.ts`, `tsx`] files. For example, if you create `pages/about.js`, then that React component will be available at `/about`. This allows for very rapid iteration once you understand this, and you can easily re-use components across pages to keep your code dry but still build very quickly.

But where do you put the form handling backend code? And how can you securely process values in your backend form while still open-sourcing your entire site?

For API handling, Next.js allows you to add code to `pages/api/` . For example, the following code, written in `pages/api/user.js` would return  a JSON response representing the user when you git `/api/user`  Any code you write here needs to satisfy the interface demonstrated above, of a function accepting a request and a response object.

So what does Next.js / Vercel do with code you write in `pages/api/`? it converts it to a lambda essentially, or a Vercel function, which you can easily view the logs for in the Vercel dashboard or via the CLI.

And when you develop locally? Then next.js just runs your function locally so you can see `console.log` output in the same terminal where you ran `npm run dev`.

All this means that you can write frontend and backend code in an organized way in a single repository, have an outstanding experience running it locally and in production, and still handle secrets via environment variables so that you never check anything sensitive into version control.

Here's the form processing code for my simple email subscribe form:

Within the Vercel dashboard, without doing any additional work whatsoever, you now get first class logging for every request to your functions:

### The vercel integration is scary good

I mean that literally. I'm slightly unnerved by how smooth of an experience deploying to Vercel was, because now I find myself wanting to launch tons of sites and projects there - which I'm certain is exactly their business model. But I am also impressed. As a first time user, I will also be a return user, and the cognitive barrier to entry for launching my next projects just went way, way down.

### What does this all mean together? Next.js is fast - stupidly fast - to work with

When you combine all the previous points you can start to see how you could rapidly build and easily maintain many high quality and performant sites at the same time. The iteration loop with this stack is insanely fast, and so much work has been put into the framework and tools when I'm getting these google lighthouse scores out of the box for desktop and mobile. And subjectively, loading my site on my desktop or phone feels really fast. I'm itching now to fire up the next project...

slack

master

main.gif'

stashed

changes.gif'

Even experienced programmers make mistakes - lately I've been forgetting to git pull on my local repos when working quickly, leading my poor colleague Max to have to Slack me messages like this while we're on pair-coding calls:

Sorry, Max! Forgetting to git pull means we're more likely to open a pull request with outdated code. We can do better than this, and use automation to augment our fallible human nature.

## Introducing autogit

Autogit is [an open-source shell script](https://github.com/zackproser/automations) that handles all your common git concerns, such as:

stashing uncommitted changes

determining the default branch and pulling the latest

fetching all new branches and pruning outdated branches

handling the case where a GitHub remote has changed its default branch from master to main

It is designed to be run everytime you change directories into a local git repository.

In this way, autogit attempts to stay out of your way, run as quickly as possible, never run when unnecessary, and otherwise remove the cognitive burden around remembering to git pull when you're in the middle of an incident and trying to move quickly.

You can easily wire up autogit to your `cd` command, if you'd like to run it that way. If you'd rather configure your environment to run it at a different point, that's fine, too!

## Autogit features

### 1. Automatically handles default branch changes from master to main

Oftentimes, developers find themselves cloning and working with repositories that have non-uniform default branches in GitHub.

Project A may use a default branch of master, while Project B, C, and D use main. What's a poor programmer to do when they're trying to put out a fire?

Just use autogit - it can detect this edge case, checkout the correct default branch locally, and pull the latest changes for you.

### 2. Designed to handle dirty worktrees and uncommitted changes

Autogit is designed to assist working developers. If you have a feature branch checked out in the git repo that you `cd` into, autogit will automatically stash your changes, checkout your default branch and pull, then checkout your original branch and re-apply your stashed changes.

### 3. Smart about when to work and when to do nothing

Autogit can tell when you already have the latest changes, and you're just changing into a sub-directory of the same git repository.

There's no real work to do in this case, so autogit returns early to keep your developer workflow snappy. In additon, autogit only runs on git repositories.

You should notice autogit fetching your latest changes whenever you change into a repo, and otherwise you should never notice autogit at all!

### 4. Auto fetching and pruning

Never miss a new feature branch or operate on a stale branch again. Autogit also fetches automatically when it is updating your local repo.

## Thanks, and a request

Thank you for reading! If you've found this post helpful at all, please consider:

1. Sharing it with a friend 2. Signing up for my newsletter 3. [Sponsoring me on GitHub](https://github.com/sponsors/zackproser)

Every little bit helps and I greatly appreciate your taking the time to read my post!

get

to

keep

the

neural

my

first

big

a

distributed

the

ref

arch

experience

comes

with

 Going the extra mile only to be unrewarded by your company feels like a personal slight and a waste of your time. It is not.

## Fuck this, I've had enough, and I can do better

When I was a younger developer at one of my first legitimate jobs - the first where my title contained the word 'engineer', we would often stay late working on complex bugs and projects that the greater team and organization didn't appreciate or even know about.

We'd handle LAMP stack development for one team on tight deadlines and work with the operations team to tackle tricky bugs at the intersection of development and deployment automation circa Puppet becoming a big thing, because nobody else wanted to touch it.

Even though our humble three person team didn't have the most impressive sounding title, when there was a greenfield project that was critical to the company, we spearheaded the efforts to pull the complex Docker build system out of thin air because nobody else was familiar enough with web technologies to make it happen.

We also handled development of the frontend for this project, all while fielding our usual support requests from multiple teams and getting, what felt like, a minimum of respect. We had the keen and likely accurate sense that most other non-technical teams in the company looked at us as code monkeys or website janitors at best.

One late evening, in a fit of frustration, I expressed as much to my senior teammate - since he and I were the only ones still around at that hour and working through issues trying to make this Hail Mary a reality.

(Spoiler alert: we did a great job on the project and delivered it on time somehow, as everyone agreed, but ultimately it was a flop and the company shelved it right before being acquired).

This teammate happened to be a more seasoned and insanely talented developer and designer, by the name of [Kyle McGuire](https://www.linkedin.com/in/kyle-mcguire-ux/). He turned to me and said, "You get to keep the neural connections", gesturing at his temple.

How true would I find his words to be - time and time again?

## Fuck this, I've had enough, and I can do better

Not so many months later, I found myself sitting across from another abnormally talented developer, [Tony Stuck](https://www.linkedin.com/in/toekneestuck/), who was at that time the lead of one of the development teams at Cloudflare. Tony held my resume, was friendly but studious and carefully read it. Then he asked me, to my immense delight and relief, several technical questions that actually got at whether or not I had relevant experience:

"If you got a report of a web application being slow or buggy, in as much detail as possible, what would you do from the front to back to debug what was wrong?"

"If you were building a new application we were going to host, list everything you would do in order to secure it"

These two questions were enough to keep us occupied for about forty minutes, because I outlined in excrutiating detail, from the frontend to the backend, everything that I had learned how to do in my previous job, and in my personal side projects, and in my reading on the side in my free time. I loved Tony's questions because, if you had never really done any development before, you couldn't bullshit them, neither were they brain teaser computer science trivia horseshit.

This person really wanted to understand if I had spent a significant amount of time doing development in a professional context. He was ultimately satisified with my answers, I was hired at Cloudflare as an engineer back when the entire engineering org was still 100 people, and I spent the next three years absorbing everything I could, doing frontend, backend and CLI development, building reproducible cloud environments with Infrastructure as Code, teaching courses on distributed logging and monitoring, [writing the first Cloudflare workers in front of www.cloudflare.com and api.cloudflare.com](/videos/sf-node-meetup-cloudflare-workers), building new features into critical legacy and high-scale systems, being mentored and, eventually, mentoring.

I did on-call rotations and carried a pager in addition to doing very long days at the office. I spent more than one weekend expanding my skillset and hacking on side projects because I wanted to be better. I ultimately even developed autoimmune disease - not only because of the way I was working but due to events in my personal life that were occurring at the same time. I did get a significant raise halfway through my tenure after asking for it, but ultimately I came to feel underappreciated and underpaid.

Oh, also here, a project that I had worked on for about a year and half that was considered extremely critical at inception was shelved by a new executive in a meeeting I wasn't in. They didn't bother telling me about it, so I found out after visiting the product manager I'd been working with it on for over a year and a half to give him the latest status update.

## Fuck this, I've had enough, and I can do better

At some point, for various reasons, I felt that it was time to go to a better opportunity, where I could learn more, make more money and have more freedom around where and when I worked.

When I got to this third job, it wasn't exactly what I had expected, and many folks who got hired around the same time as I did struggled in the beginning.

I also struggled in the beginning and I was surprised at how intense a fully remote job could be. Yet everything I needed to do there, with some glaring exceptions that showed up on my first performance review, had some connection to the loops of intensive focus, learning, stress, achievement and ultimate success that I had gone through time and time again at Cloudflare in many projects and in many cross-team interactions.

When I started there, only the two Principal engineers who had written our most complex minter of distributed systems from multi-layer blueprints were capable of working with its various codebases and performing and debugging deployments for customers successfully.

I threw myself completely into learning this beast, documenting it, shadowing them on deployments, pairing with them, taking the small shitty jobs to fix broken things as an excuse to get closer to it. When I left after three and a half years of working with this project, which was a bread and butter sales item for the company, I was one of the only engineers who was capable of working with its many codebases. The two original Principals had long since gone. I held the record for number of successful solo deployments.

I lost count of the number of evenings I pushed late into the night (or, as I got older and wiser, returned after a long break for dinner and family time) to continue debugging and pushing forward deployments. I wrote extra tooling and automations to improve things. I worked in Go, Bash, in AWS, using Docker, Packer, Kubernetes, RDS, Lambda, Python, Terraform, and Lua where needed. I once pulled off 6 deployments solo, in one week, because I was the only engineer left who knew how to do this and they were still selling frequently. I acquired 4 AWS certifications while working here in order to expand my knowledge of my primary environment of operations.

When I got done with a long day of working in GitHub for work, I took a break, spent time with my family, and when I had spare time on the weekends I transitioned to working in GitHub on my own side projects to further my skills. I used the company's excellent learning benefit to hire a programming mentor, who assigned me additional programming challenges and projects on top of my work and what I was doing on the side. Tip, [John Arundel](https://bitfieldconsulting.com) is top-notch, if you're looking for a professional programming mentor or Golang teacher.

The company did a good deal of hand-wringing around my paternity leave, which had been scheduled way in advance, because we were unable to sufficiently staff up folks who had the chops and the pain tolerance to work with this system.

I produced hours of video content for peers and spent countless hours pairing with new hires and folks leveled far above me trying to get them up to speed and conversant with the same system. We were never successful in our efforts to give this absolute beast of a machine that itself produced distributed, paramterizable architectures, the love and care that it needed.

I wrote a small book's worth of documentation in Notion, plus runbooks with screenshots, and tools, and diagnostic CLIs to try to lower the burden for newcomers and the long-term team members who remained unable to do deployments.

Praise, shoutouts, words of thanks, effusive respect on Zoom calls and promotions were plentiful, but a meaningful raise remained elusive. Poetically, I learned on my last week that the company had decided to stop selling this product to customers, which you might have thought felt perfectly Sisyphean.

While I didn't love seeing this imperfect beast that I'd spent almost 4 years of my life trying to tame get put down right in front of me, I don't even resent working this hard or the time I spent on evenings and weekends researching and practicing, because I love what I do and I love to make things. I love to practice my craft and I love to learn new things. And you get to keep the neural connections.

## Fuck this, I've had enough, and I can do better

I'm at a new place now and I'm happy with my title and the work I am doing. I enjoy my teammates they seem to appreciate the experience and the skills I bring to the table.

Because I'm a staff developer now in addition to my content production responsibilities, I can, to some degree, float around and find major issues that are plaguing other teams, from a client lacking in a modern development and packaging solution, to a site that needs serious diagnosis and backend fixes to speed it up, and the work I do feels meaningful, important and deeply appreciated.

And it's honestly not very difficult for me to do. Because you get to keep the neural connections.

[Visit the repo on GitHub](https://github.com/zackproser/super_catfacts)

I rewrote CatFacts from scratch in Golang just for the practice. I wanted an excuse to understand Go modules.

In keeping with the spirit of going way over the top, this service is deployed via Kubernetes on Google Cloud, for the most resilient pranking service possible.

## Read my complete write-up on Medium

I wrote up a technical deep dive on this project on Medium. You can [check it out here](https://medium.com/swlh/building-a-catfacts-pranking-service-in-golang-5b51e8721fe2?source=friends_link&sk=8b97968fa6fdbf51cbb0bd3b5983c674).

---

I built Pinecone's first AWS Reference Architecture using Pulumi.

This is the sixth article I wrote while working at Pinecone:

Read article

---

## Table of contents

## What is a vector database?

In the world of data, not everything fits neatly into rows and columns. This is especially true when dealing with complex, unstructured data like images, videos, and natural language. That's where vector databases come in.

A vector database is a type of database that stores data as high-dimensional vectors, which are essentially lists of numbers that represent the features or characteristics of an object. Each vector corresponds to a unique entity, like a piece of text, an image, or a video.

But why use vectors? The magic lies in their ability to capture semantic meaning and similarity. By representing data as vectors, we can mathematically compare them and determine how similar or dissimilar they are. This enables us to perform complex queries like "find me images similar to this one" or "retrieve documents that are semantically related to this text."

## Why are vector databases so hot right now?

Vector databases have gained significant popularity in recent years, particularly in the fields of artificial intelligence (AI) and machine learning (ML). As AI and ML models become more sophisticated, they require efficient ways to store, search, and retrieve the vast amounts of unstructured data they work with.

Traditional databases, which are designed for structured data, often struggle to handle the complexities and scale of vector data. Vector databases, on the other hand, are purpose-built for this task. They offer specialized indexing and search algorithms that can quickly find similar vectors, even in databases with billions of entries.

## Vector database use cases

The ability to search for similar vectors opens up a world of possibilities for AI and ML applications. Some common use cases include:

Recommendation Systems: By representing user preferences and item features as vectors, vector databases can power highly personalized recommendation engines.

Image and Video Search: Vector databases enable searching for visually similar images or videos, revolutionizing content

based retrieval.

Natural Language Processing: By encoding text into vectors, vector databases facilitate semantic search, topic modeling, and document clustering.

Fraud Detection: Vector databases can help identify patterns and anomalies in financial transactions, aiding in fraud detection efforts.

## Choosing the right vector database

With the growing demand for vector databases, several options have emerged in the market. Each database has its own strengths, trade-offs, and ideal use cases. In this blog post, we'll dive into a comprehensive comparison of popular vector databases, including Pinecone, Milvus, Chroma, Weaviate, Faiss, Elasticsearch, and Qdrant.

By understanding the features, performance, scalability, and ecosystem of each vector database, you'll be better equipped to choose the right one for your specific needs.

These are just a few examples of how vector databases are driving innovation across industries.

## Deployment Options

Pinecone is the odd one out in this regard. Because Pinecone is a fully-managed service for performance and scalbility reasons, you can't run an instance locally.

Milvus, Chroma, Weaviate, Faiss, Elasticsearch and Qdrant can all be run locally; most provide Docker images for doing so.

| Vector Database | Local Deployment | Cloud Deployment | On-Premises Deployment | |-----------------|------------------|------------------|-----------------------| | Pinecone        | ❌               | ✅ (Managed)     | ❌                    | | Milvus          | ✅               | ✅ (Self-hosted) | ✅                    | | Chroma          | ✅               | ✅ (Self-hosted) | ✅                    | | Weaviate        | ✅               | ✅ (Self-hosted) | ✅                    | | Faiss           | ✅               | ❌               | ✅                    | | Elasticsearch   | ✅               | ✅ (Self-hosted) | ✅                    | | Qdrant          | ✅               | ✅ (Self-hosted) | ✅                    |

## Scalability

Meaningful scalability metrics require defined constraints. The following is a quick look at which scaling schemes each vector database supports.

Stay tuned for performance and benchmarking stats.

| Vector Database | Horizontal Scaling | Vertical Scaling | Distributed Architecture | |-----------------|--------------------|-----------------|-----------------------| | Pinecone        | ✅                 | ✅              | ✅                    | | Milvus          | ✅                 | ✅              | ✅                    | | Chroma          | ✅                 | ✅              | ✅                    | | Weaviate        | ✅                 | ✅              | ✅                    | | Faiss           | ❌                 | ✅              | ❌                    | | Elasticsearch   | ✅                 | ✅              | ✅                    | | Qdrant          | ✅                 | ✅              | ✅                    |

## Performance and benchmarking

Coming soon!

## Data Management

Pinecone supports "collections" which are like checkpoints or save states on your indexes.

You can use collections to create new indexes. Generally speaking, they're a convenience method for moving lots of data between indexes.

| Vector Database | Data Import | Data Update/Deletion | Data Backup/Restore | |-----------------|-------------|----------------------|---------------------| | Pinecone        | ✅          | ✅                   | ✅                  | | Milvus          | ✅          | ✅                   | ✅                  | | Chroma          | ✅          | ✅                   | ✅                  | | Weaviate        | ✅          | ✅                   | ✅                  | | Faiss           | ✅          | ✅                   | ❌                  | | Elasticsearch   | ✅          | ✅                   | ✅                  | | Qdrant          | ✅          | ✅                   | ✅                  |

## Vector Similarity Search

One of the reasons vector databases are so useful is that they can tell us about the relationships between things and how similar or dissimilar they are.

There are a variety of distance metrics that allow vector databases to do this, and different vector databases will implement various distance metrics.

For a helpful introduction to how the different distance metrics compare, check out [Pinecone's guide here](https://www.pinecone.io/learn/vector-similarity/).

| Vector Database | Distance Metrics | ANN Algorithms | Filtering | Post-Processing | |-----------------|------------------|----------------|-----------|-----------------| | Pinecone        | Cosine, Euclidean, Dot Product | Proprietary (Pinecone Graph Algorithm) | ✅ | ✅ | | Milvus          | Euclidean, Cosine, IP, L2, Hamming, Jaccard, Tanimoto | HNSW, IVF_FLAT, IVF_SQ8, IVF_PQ, RNSG, ANNOY | ✅ | ✅ | | Chroma          | Cosine, Euclidean, Dot Product | HNSW | ✅ | ✅ | | Weaviate        | Cosine | HNSW | ✅ | ✅ | | Faiss           | L2, Cosine, IP, L1, Linf | IVF, HNSW, IMI, PQ | ❌ | ❌ | | Elasticsearch   | Cosine, Dot Product, L1, L2 | HNSW | ✅ | ✅ | ✅ | ✅ | | Qdrant          | Cosine, Dot Product, L2 | HNSW | ✅ | ✅ | ✅ |

## Integration and API

While REST APIs are more commonly encountered, GRPC APIs are geared toward performance and throughput in latency critical scenarios or when its necessary to move large amounts of data quickly.

Depending on your requirements and network, GRPC can be several times faster than REST.

| Vector Database | Language SDKs | REST API | GraphQL API | GRPC API | |-----------------|---------------|----------|-------------|---------| | Pinecone        | Python, Node.js, Go, Rust | ✅ | ❌ | ✅  | | Milvus          | Python, Java, Go, C++, Node.js, RESTful | ✅ | ❌ | ✅  | | Chroma          | Python | ✅ | ❌ | ❌ | | Weaviate        | Python, Java, Go, JavaScript, .NET | ✅ | ✅ | ✅ | | Faiss           | C++, Python | ❌ | ❌ | ✅ | | Elasticsearch   | Java, Python, Go, Ruby, PHP, Rust, Perl | ✅ | ❌ | ❌ | | Qdrant          | Python, Rust | ✅ | ❌ | ✅ |

## Security

Pinecone encrypts vectors in flight and at rest.

| Vector Database | Authentication | Data Encryption | Access Control | |-----------------|----------------|-----------------|----------------| | Pinecone        | ✅             | ✅              | ✅             | | Milvus          | ✅             | ✅              | ✅             | | Chroma          | ❌             | ❌              | ❌             | | Weaviate        | ✅             | ✅              | ✅             | | Faiss           | ❌             | ❌              | ❌             | | Elasticsearch   | ✅             | ✅              | ✅             | | Qdrant          | ✅             | ✅              | ✅             |

## Community and Ecosystem

Pinecone itself is not open-source, meaning that you cannot browse to the source code for the core database or supporting services or contribute to them.

That said, [Pinecone is very active in terms of open-source](https://github.com/pinecone-io/), publishing client SDKs in the multiple languages, as well as tools like [Canopy](https://github.com/pinecone-io/canopy), which facilitates creating a [Retrieval Augmented Generation](https://pinecone.io/learn/retrieval-augmented-generation) (RAG) app very quickly.

| Vector Database | Open-Source | Community Support | Integration with Frameworks | |-----------------|-------------|-------------------|------------------------------| | Pinecone        | ❌          | ✅                | ✅                          | | Milvus          | ✅          | ✅                | ✅                          | | Chroma          | ✅          | ✅                | ✅                          | | Weaviate        | ✅          | ✅                | ✅                          | | Faiss           | ✅          | ✅                | ✅                          | | Elasticsearch   | ✅          | ✅                | ✅                          | | Qdrant          | ✅          | ✅                | ✅                          |

## Pricing

Pinecone offers a free tier which allows you to create and maintain one index, and to store roughly 100,000 vectors in it.

Since Pinecone is fully-managed, you must either use the free tier or pay for access to higher tier plans.

| Vector Database | Free Tier | Pay-as-you-go | Enterprise Plans | |-----------------|-----------|---------------|------------------| | Pinecone        | ✅        | ✅            | ✅               | | Milvus          | ✅        | ❌            | ❌               | | Chroma          | ✅        | ❌            | ❌               | | Weaviate        | ✅        | ❌            | ✅               | | Faiss           | ✅        | ❌            | ❌               | | Elasticsearch   | ✅        | ✅            | ✅               | | Qdrant          | ✅        | ❌            | ❌               |

## Additional Features

Metadata is any arbitrary strings, numbers, or objects that you want to associate with a vector. You can think of it like a JavaScript object:

You define your metadata in your application code and attach it to a vector at upsert time, meaning when you make your request to your vector database to store a vector.

Metadata is an incredibly powerful concept and very complimentary with core vector database features; it's the link between ambiguous human language and structured data.

This is the foundation of the architecture where a human user asks for a product and the AI shopping assistant immediately returns the items they're describing.

| Vector Database | Metadata Support | Batch Processing |  Monitoring and Logging | |-----------------|------------------|------------------|-------------------------| | Pinecone        | ✅               | ✅               |  ✅                     | | Milvus          | ✅               | ✅               |  ✅                     | | Chroma          | ✅               | ✅               |  ❌                     | | Weaviate        | ✅               | ✅               |  ✅                     | | Faiss           | ❌               | ✅               |  ❌                     | | Elasticsearch   | ✅               | ✅               |  ✅                     | | Qdrant          | ✅               | ✅               |  ✅                     |

## Check back soon

I'm treating this post as a living piece of content - so it will receive some updates over time. Be sure to bookmark / share it if you'd like to stay up-to-date.

---

This was the fifth article I published while working at Pinecone:

Read article

---

This was the second article I published while working at Pinecone:

Read article

scott

scott

oracle

oracle

## What is this?

The Office Oracle AI chatbot is a complete AI chatbot built on top of langchain, Pinecone.io and OpenAI's GPT-3 model. It demonstrates how you can build a fully-featured chat-GPT-like experience for yourself, to produce an AI chatbot with any identity, who can answer factually for any arbitrary corpus of knowledge.

For the purposes of demonstration, I used the popular Office television series, but this same stack and approach will work for AI chatbots who can answer for a company's documentation, or specific processes, products, policies and more.

## Video series

Be sure to check out my three-part video series on YouTube, where I break down the entire app end to end, and discuss the Jupyter notebooks and data science elements, in addition to the Vercel ai-chatbot template I used and modified for this project:

[AI Chatbots playlist on YouTube](https://youtube.com/playlist?list=PLAEMpWvY5KHwwDFTNaPquREnk5Y9eiipa)

### Intro video and demo

### Jupyter notebooks deep

dive

### Next.js vercel template ai-chatbot deep-dive

## Open source code

I open sourced the Jupyter notebooks that I used to prepare, sanitize and spot-check my data here:

[Office Oracle Data workbench](https://github.com/zackproser/office

oracle

data

bench)

[Office Oracle Data test workbench](https://github.com/zackproser/office

oracle

db

test

notebook)

The data workbench notebook handles fetching, parsing and writing the data to local files, as well as converting the text to embeddings and upsertings the vectors into the Pinecone.io vector database.

The test workbench notebook demonstrates how to create a streamlined test harness that allows you to spot check and tweak your data model without requiring significant development changes to your application layer.

I also open sourced [the next.js application itself](https://github.com/zackproser/office-oracle)

---

Codeium began its life as an AI developer tool that offered code-completion for software developers, and ChatGPT was originally a general purpose AI language model that could assist with a variety of tasks.

But as I write this post on February 2nd, 2024, many of these products' unique capabilities are beginning to overlap. What are the key differences and what do you need to know in order to get the most out of them both?

When you're finished reading this post you'll understand why these tools are so powerful, which capabilities remain unique to each, and how you can use them to level up your development or technical workflow.

## Codeium vs ChatGPT

capabilities at a glance

|   | Code generation  | Image generation  | Chat capabilities  | Code completion  | General purpose chat | Runs in IDEs | Free |---|---|---|---|---|---|---|---| | **Codeium**  | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | | **ChatGPT**  | ✅ | ✅ | ✅ | ✅ | ✅ | ✴️  | ❌ |

**Legend** | Supported | Not supported | Requires extra tooling | |---|---|---| |    ✅    | ❌ | ✴️  |

Let's break down each of these attributes in turn to better understand how these two tools differ:

### Code generation

Both Codeium and ChatGPT are capable of advanced code generation, meaning that developers can ask the tool to write code in most any programming language and get back something pretty reasonable most of the time.

For example, in the browser interface of ChatGPT 4, you could ask for a Javascript class that represents a user for a new backend system you're writing and get something decent back, especially if you provide notes and refinements along the way.

For example, here's an actual conversation with ChatGPT 4 where I do just that.

Unless you're using a third party wrapper like a command line interface (CLI) or IDE plugin that calls the OpenAI API, it's slightly awkward to do this in ChatGPT's browser chat window - because you're going to end up doing a lot of copying from the browser and judiciously pasting into your code editor.

Even with this limitation, I've still found using ChatGPT 4 to discuss technical scenarios as I work to be a massive accelerator.

### Runs in IDEs

Codeium's advantage here is that it tightly integrates with the code editors that developers already use, such as VSCode and Neovim.

Think of Codeium as a code assistant that is hanging out in the background of whatever file you happen to be editing at the moment. It can read all of the text and code in the file to build up context.

As you type, you will begin to see Codeium suggestions, which are written out in a separate color (light grey by default) ahead of your cursor.

As the developer, if you feel that the suggestion is a good one, or what you were about to type yourself, you hit the hotkeys you've configured to accept the suggestion and Codeium writes it out for you, saving you time.

In a good coding or documentation writing session, where Codeium is correctly following along with you and getting the right context, these many little autocompletions add up to saving you quite a bit of time.

Like GitHub CoPilot, you can also write out a large comment block describing the code or functionality you want beneath it, and that is usually more than enough for Codeium to outright write your function, method or class as you've described it, which can also be very accelerating, e.g.,:

 Once you move your cursor below this, Codeium will start writing out the code necessary to fulfill your description.

### With some extra work, you can bring ChatGPT into your terminal or code editor

This is not to say that you can't get ChatGPT into your terminal or code editor - because I happen to use it there everyday. It just means you need to leverage one of many third party tools that call OpenAI's API to do so.

My favorite of these is called `mods`.

This makes the full power of OpenAI's latest models, as well as many powerful local-only and open-source models, available in your terminal where developers tend to live.

I can have it read a file and suggest code improvements:

or assign it the kinds of tasks I previously would have had to stop and do manually:

There are many community plugins for VSCode and Neovim that wrap the OpenAI in a more complete way, allowing you to highlight code in your editor and have ChatGPT4 look at it, rewrite it, etc.

### Is it free to use?

When you consider that it's possible to bring ChatGPT4 into your code editors and terminal with a little extra work, one of the key advantages that Codeium retains is its price.

I'm currently happy to pay $20 per month for ChatGPT Plus because I'm getting value out of it daily for various development tasks and for talking through problems.

But Codeium is absolutely free for individual developers, which is not to be overlooked, because the quality of its output is also very high.

## What advantage does ChatGPT have over Codeium?

As of this writing, one of the most powerful things that ChatGPT can do that Codeium can't is rapidly create high quality images in just about any artistic style. Users describe the image they want, such as:

> "A bright and active school where several young hackers are sitting around working on computers while the instructor explains code on the whiteboard. Pixel art style."

Having an on-demand image generator that responds to feedback, has a wide array of artistic styles at its disposal and can more or less follow directions (it's definitely not perfect) is a pretty incredible time-saver and assistant when you publish as much on the web as I do.

## What about general purpose chat?

Up until recently, ChatGPT had the upper hand here. It's still one of the most powerful models available at the time of this writing, and it is not constrained to technical conversations.

In fact, one of my favorite ways to use it is as a tutor on some new topic I'm ramping up on - I can ask it complex questions to check my understanding and ask for feedback on the mental models I'm building. Anything from pop culture to outer space, philosophy and the meaning of life are up for grabs - and you can have a pretty satisfying and generally informative discussion with ChatGPT on these and many more topics.

Tools like Codeium and GitHub's CoPilot used to be focused on the intelligent auto-completion functionality for coders, but all of these "AI-assisted developer tools" have been scrambling to add their own chat functionality recently.

Codeium now has free chat functionality - and from some initial testing, it does quite well with the kinds of coding asisstant tasks I would normally delegate to ChatGPT:

## Should you use Codeium or ChatGPT?

Honestly, why not both? As I wrote in [Codeium and ChatGPT are all I need](/blog/chatgpt-4-and-codeium-are-my-favorite-stack), these two tools are incredibly powerful on their own, and they're even more powerful when combined.

I expect that over time we'll begin to see more comprehensive suites of AI tools and assistants that share context, private knowledge bases and are explicitly aware of one another.

Until then, I'm getting great effect by combining my favorite tools in my daily workflow.

## How do I use Codeium and ChatGPT together?

As I write this blog post on my Linux laptop in Neovim, I first tab over to Firefox to ask ChatGPT to generate me a hero image I can use in this blog post. I do this in the chat.openai.com web interface, because that interface is tightly integrated with DALLE, OpenAI's image generating model.

I'll let it do a first few iterations, giving notes as we go, and as I write, until we get the right image dialed in.

Meanwhile, as I write out this blog post in Neovim, Codeium is constantly suggesting completions, which is generally less useful when I'm writing prose, but very useful whenever I'm coding, writing documentation, writing scripts, etc.

## Overview

This post will walk through my open-sourced Article Optimizer app, taking a look at some of its key architectural decisions and features. This app is running at [www.article-optimize.com](//www.article-optimize.com) if you’d like to try it out. I’ve also open-sourced it, so you can follow along in [the Github repo here](//www.github.com/zackproser/articleoptimizer).

## What is the Article Optimizer?

### Background

This is a web application built in PHP 7 leveraging the [Symfony web framework](//symfony.com). Its intended user base is content writers, especially those whose content will ultimately live online. At the end of the day, a complex interplay between human signals, such as comments and social shares, and search engine ranking systems will ultimately determine how “successful” a given piece of content is, from a rankings perspective at least. This tool was developed to assist authors in understanding how their content is likely to be parsed and ranked by search engines.

### How it works

You can [submit an article on any topic through the tool](//www.article-optimize.com) and in around 1 second you’ll be presented with a detailed analysis of your work, breaking down its:

rough word count

content category (Food & Drink, Politics, Hobbies etc)

sentiment analysis of major concepts, keywords, entities (positive, negative, neutral)

keyword / phrase density

In addition, the tool will do its best to find copyright-free images that are on-topic. If you write an article about growing tomatoes at home, the tool will try to find you a good handful of high quality tomato images that you are free to include and publish in your article. Including on-topic high quality media in your content ultimately leads to a better reading experience on the web and can improve your article’s search rankings.

Finally, the report generated by the tool contains a reference to your full article and is written to a unique URL that can be easily shared via the programmatically generated Bitly link, the social share buttons, or by using a built-in feature that allows you to send the report by email to a friend, colleague or client. This aspect is intended to empower authors to show off the quality of their work as validated by a third party service.

Here's an example report:

### Why use it

This tool is intended to be used as a “spot check” for authors when they are getting close to wrapping up their article or are getting ready to publish it. They may not be aware, for example, that they are unintentionally “keyword stuffing” their article by repeating a given phrase with unnatural frequency, or that their portrayal of a given person is coming across as negative.

## Main Technical Considerations

### How much work has to be done and how long can I put it off for?

In creating this tool, one of my primary concerns was speed. Specifically, the time between the moment an author submits their article for processing and the moment at which they can view the completed analysis of their work should be as short as possible.

A general rule of thumb is that users of web applications begin to perceive the app they’re interacting with as sluggish or non-responsive if there’s anything more than a second of latency between the time they initiate an action through the user interface and the time they receive the next visual feedback from the app. For a more in-depth article on this sense of responsiveness from a user experience perspective, check out [this article](//www.nngroup.com/articles/response-times-3-important-limits/).

The Article Optimizer renders reports for most articles in 1 second or less. This performance is the result of careful planning and thinking through what processing has to happen before the report can be rendered, and what functionality can be offloaded until afterwards.

### Identifying the bare minimum you can do before rendering

In the case of this application, I’m using [IBM Watson’s](https://www.ibm.com/watson/) [AlchemyLanguage API](https://www.ibm.com/watson/developercloud/alchemy-language.html) to do most of the heavy lifting in terms of linguistic processing. Since this means I have to do at least one network request to an external service following a user submission and before rendering the report, I wasn’t willing to call any others during processing. Everything else (Bitly, Flickr) would have to be done via ajax after the initial report was rendered.

That’s why the bulk of the processing is done so quickly and why the tool feels snappy and quick to use. When a user submits an article, I do validation of the content both on the client and on the server. If there are any show stopping issues, such as if the user submitted bogus content that’s too short, they’ll get a helpful and descriptive error message up front and everything grinds to a halt until they fix their submission.

Assuming the user submits a valid-looking article of sufficient length, I sanitize and filter it to remove any crap, remnant HTML tags from a hurried copy and paste operation, or malicious embedded scripts because we never trust user input. Only then do I hand off the article to AlchemyLanguage for a detailed breakdown. Once I receive the response, I do a minimum of report preparation work: examining the AlchemyLanguage response, bundling the key information into a sane format as expected by the report templates. Once this is done, I can render the permanent report, write its contents to a static HTML file that will live in perpetuity for the user to share, and redirect the user to this completed report.

It’s important to step back at this point to understand: at this time the user’s article has been fully processed, their report written to the server, and the user is actually looking at the report and beginning to read it, but none of these things have happened yet:

1. The Bitly short link for this specific report has not been generated or displayed 2. The Twitter share button has not had its contents altered by jQuery to include this Bitly link 3. None of the copyright-free images have even been searched for yet

The user doesn’t notice these things because it will take several more seconds, at the earliest, before they begin to need any of this information. As soon as the report page is finished loading, jQuery handlers go to work fetching and updating all of this data via the help of server-side controllers designated for each task and associated service. The point is that most of the time the user will never notice the brief delay, because they need to scroll through a whole lot of report details before getting to the images section at the bottom. All the average user knows is that the tool they’re using processes articles very quickly.

### Keeping it clean and easily extendable

Part of the reason I chose the [Symfony framework](https://symfony.com) for this rewrite is that Symfony does a good job of enforcing some sanity and organization around web projects without being so dogmatic that you can’t customize things to your liking.

In case I decided to return to this application after 2 years to add some new features, I know my future self would want to open a project that is clean, well-organized, thoroughly documented and demonstrating good separation of concerns. This means lots of DocBlocks throughout the application, giving other developers everything they need to know about a given class or method up front.

Starting with the PHP side of things, the project begins with [the Default controller](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Controller/DefaultController.php). This defines handlers for the main routes of the application, and the behavior of the index page and its 3 forms. One of Symfony’s main strengths is the significant work they have put into forms and form processing, which allowed me to define rich form functionality in less code, keeping the Default controller reasonably slim.

Once we have a valid submission, work flows to [the Analyzer class](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/Analyzer.php), which is concerned with breaking down and processing the article text, interacting with the AlchemyLanguage API, and bundling the final analysis into a format expected by [the report templates](//github.com/zackproser/articleoptimizer/blob/github/app/Resources/views/default/report.html.twig).

### Keeping it easy to reason about

Loosely following the idea that a literate program should read like a short story, I think of the steps described in [the Analyzer class](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/Analyzer.php) as a recipe for processing a user’s article in a generic way that will result in useful insight. My thinking here is that if one or more other programmers were to begin working on this project, they should be able to easily read the Analyzer class to quickly gain an understanding of the general processing steps that occur for each article.

### Separation of concerns

At the same time, I want to maintain a balance between legibility and concision. That’s why the nitty gritty details of lower level actions are abstracted away by the higher level Analyzer class, but detailed in [the AnalysisHelper class](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/AnalysisHelper.php). Furthermore, curl calls are abstracted into a [Curl Helper class](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/CurlHelper.php), just as [Flickr](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/Flickr.php) and [Bitly](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Classes/Bitly.php) calls are abstracted into their own classes which use the Curl helper class. The main idea is to build reusable components and then load them wherever leveraging them makes sense.

We don’t want to configure and execute raw curl calls in every method that makes a network request, because it’s not as easily maintainable and will also result in a lot of duplicated code. If we wind up needing to change a header across all of our curl calls, we’d need to find every instance of a raw curl call to change it - or we may miss some and be left with inconsistent behavior.

### Leverage the framework

Symfony also features the [twig templating language](//symfony.com/doc/current/templating.html), and it’s excellent. Though in general we want to keep as much logic out of our templates as possible, many of the built in-functions (such as count) are useful for determining if we have enough data to render a full section, or if we should display an error instead.

### Even in templates, we want to build reusable components

After getting everything working and displaying the way I wanted it, I started looking for duplicated code in my templates. The ribbons that introduce each new report section, for example, are all roughly the same - though their overlaid text changes. This makes them a good candidate for refactoring: moving the code that generates a ribbon into a custom twig function “ribbon” that we can call with a single string argument for the text that should appear on the ribbon.

Twig lets you create [an AppExtension class](//github.com/zackproser/articleoptimizer/blob/github/src/AppBundle/Twig/AppExtension.php) that defines your custom twig functions. In addition to cutting down on duplicate code and helping you to keep your templates clean, leveraging custom twig functions is also a great way to ensure your team members can call the same functions when building out features, helping you maintain style uniformity throughout your application.

### Client

side Javascript

I chose tried and true jQuery for the client side javascript for a few reasons:

In general, this app is not complex or javascript heavy enough to warrant the overhead of a full frontend framework

There are no server side models that need to be updated in realtime / synced to the client, so data binding is not a concern

The bulk of this app lives on the server. The client side javascript is only tasked with handling a few modals, making some ajax calls, and doing some light validation on user input

However, just because we’re not using a the latest framework hotness doesn’t mean we should be writing disorganized spaghetti scripts. I wanted a good way to organize the jQuery selectors that my javascript would be using, without falling victim to the all to common gotchas of changing DOM selectors over the course of project development causing errors.

I found a good approach in a Stackoverflow response that boils down to defining your jQuery selectors in [one file or object](//github.com/zackproser/articleoptimizer/blob/github/web/js/index.js), and then passing that “controls” object into the actual client module. This accomplishes a couple of things:

It keeps our jQuery selectors defined in a single place. Though [the client](//github.com/zackproser/articleoptimizer/blob/github/web/js/optimizer-client.js) itself might use and manipulate a given selector a number of times in various complex functions, there’s only one place to update its selector if your DOM changes When your module is agnostic about the selectors of the given elements its operating on, its easier to write portable code - keeping things abstract makes it easier to publish the module as a library, jQuery plugin, etc Our final code is cleaner and simpler. We don’t have a confusing mixture of selectors and objects polluting our module.

In addition to this handy trick, I employed [the Revealing Module pattern](//addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript) via a [closure](//developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to define a “client” object that exposes only a single method, init, to the on-page javascript that instantiates it.

I do this because I want the module’s state to remain private, such that users can’t tamper with it and so that there is no chance for collisions between module variables and variables in the global scope. This is a handy pattern for developing javascript plugins that might need to run in the same memory space as a dozen other libraries, while avoiding the pitfalls that come from polluting global scope.

### Building configurable knobs for ease of use

Things that you expect to happen often for your application should be easy. For example, this application has two separate advertisement modules, each containing 2 ad blocks, that can enabled or disabled. If this application were running on behalf of a company, you could imagine that the marketing department would have a keen interest in swapping out the ads every time there was a new promotion or campaign running.

We don't want to deal with a complex, tedious or error-prone manual process each time we update this section, then. Therefore, we should make advertisements configurable and do the work of implementing our business logic in the controllers for displaying ads up front, before we launch. To demonstrate this approach I defined an advertisements blog in [the example-parameters.yml file here](//github.com/zackproser/articleoptimizer/blob/github/app/config/example-parameters.yml#L123).

Now when marketing opens a ticket for 4 new campaigns, you're just modifying the values in a config file, instead of wrangling a bunch of custom assets and HTML by hand.

### Building configurable knobs for your friends in DevOps

This same principle applies across any aspects of your app that you expect would need to change for any reason in the future, foreseeable or otherwise. Maybe operations needs to consolidate vendor accounts under a single new corporate credit card, which means the API keys your app depends on need to change. Would you rather have to tell Ops that you need a day or so to grep through every instance of the key and change it by hand? Or that they can simply change an environment variable, clear the production cache and restart Apache?

Anything that could change or will need to change should probably be a configurable knob: an evironment variable or parameter that someone who is not necessarily a developer with deep expertise in the given application can look up in the project documentation and modify with a high degree of confidence they won't break anything by doing so.

### Thanks for reading

If you have any questions feel free to [email me](mailto:zackproser@gmail.com). If something's not clear or you'd like to see additional clarification or detail on any particular topics, I'd like to know that, too.



## Table of contents

For the past 4 years, I've been running system76 linux machines for my professional, personal and consulting work. Here's how I got started and how they hooked me as a customer.

## 1. The Darter Pro: My Reliable Daily Driver

I bought my first System76 laptop in February of 2020. I'm still typing this blog post out on it.

It has been my reliable daily driver for 4 years through two startups and near daily hacking on my own projects, tons of technical writing, home organizational work, consulting and freelancing...

Here's the specs I bought:

15" Matte FHD 1080p Matte FHD Display

Intel® UHD Graphics

Ubuntu 18.04 LTS (64

bit) (since upgraded)

4.2 GHz i5

10210U (1.6 up to 4.2 GHz

6 MB Cache

4 Cores

8 Threads)

32 GB Dual Channel DDR4 at 2666MHz (2× 16GB)

United States Keyboard

1 TB NVMe Seq Read: 3,500 MB/s, Seq Write: 3,300 MB/s

WiFi up to 867 Mbps + Bluetooth 5

65 W Extra Charger

3 Year Limited Parts and Labor Warranty

3 Day Rush Assembly Service

The final price was $1888.00.

I've upgraded Ubuntu twice, migrated this website from Next.js pages router to app router, built a full e-commerce integration, interactive visualizations, distributed systems on AWS that were heavy on Docker builds, and the rest. I've had zero issues with the machine.

I've customized it to my liking (Neovim, Tmux, various command line tools), and, more recently Cursor.sh for AI-assisted development. I have never run Pop OS, System76's open-source operating system, but I've run Ubuntu on all my machines and had a great experience.

I order my System76 builds with Ubuntu pre-installed, but the first thing I do when I receive it is re-install Ubuntu from my own flashed drive so that I can configure full disk encryption and set a root password. This has worked very well for all the computers I've bought from System76.

Two weeks ago, I opened the case to lift up the keyboard so I could thoroughly clean the keyboard and underneath it. It has required no other maintenance in the time I've owned it.

The most common criticms I see leveraged at System76 machines is that the cases are thin and weak.

I agree with this criticism, particularly with the version of the Darter Pro that I got, but nevertheless this machine has transited the globe with me with no issues. I keep it in a soft case and I don't throw it against walls even when I really want to.

My understanding is that newer versions of the Darter Pro got a metal case upgrade.

## 2. The Meerkat: My Linux Desktop

My Darter Pro is ideal for moving around and coding or writing (it's actually weakest for conferencing + video calls in my experience), but I needed a stable desktop setup that I could specialize in:

Video calls / conferencing / pair

coding

Video recording

Video editing

Livestreaming

DevOps work (Docker builds, AWS deployments, CLI development, CI/CD jobs)

I bought a Meerkat in April 2021 and quickly set it up to be my primary desktop computer.

Again, I've had essentially zero issues with this machine since buying it. It has successfully handled everything I've thrown at it:

OBS Studio and heavy video editing workflows

Steam link chicanery

Multiple simultaneous video inputs

Livestreaming via OBS Studio WHILE performing Steam link chicanery and handling Xbox 360 controller seamlessly

Streamdeck integration to control my lights, run scripts, CLIs, etc

I've written more in depth about [the how and why of this setup here](/blog/yubikey-sudo-git-signing).

Here's the specs I bought:

Ubuntu 20.04 LTS (64

bit)

4.7 GHz i7

10710U (1.1 up to 4.7 GHz

12 MB Cache

6 Cores

12 Threads)

64 GB Dual Channel DDR4 at 2666 MHz (2x 32GB)

500 GB NVMe Seq. Read: 3500/s, Write: 3200MB/s

No Additional Storage

1 Year Limited Parts & Labor Warranty

Rush Assembly

Ships the following business day from assembly

System76 Meerkat Packaging

Meerkat painted lid

The final price was $1,431.00.

It's not even loud!

## 3. The Serval WS: A Beast, but ultimately not for me

In May 2023, I purchased a Serval WS, because I was pivoting into [a role at Pinecone](/blog/i-am-joining-pinecone-io), so I wanted a machine with a powerful GPU so that I could screw around with machine learning tasks locally to learn.

Here's the specs I bought:

Ubuntu 22.04 LTS

5.4 GHz (i9

13900HX

up to 5.4 GHz

36MB Cache

8 P

cores and 16 E

cores)

64 GB Dual Channel DDR5 up to 4800 MHz (2x 32GB)

2 TB PCIe Gen4 Up to Seq Read: 7,000 MB/s, Seq Write: 5,100 MB/s

No Additional Storage

United States QWERTY Keyboard

WiFi 6E + Bluetooth 5

280 Watt Extra Charger

2 Year Limited Parts and Labor Warranty

8 GB GeForce RTX 4070 with 4608 CUDA Cores

17.3" Matte Ultra HD 4K (144 Hz)

The final price was $3,485.00.

Ultimately, I did not keep this machine, but my only issue with it was that I opted for too large of a screen (17") which resulted in the keyboard dimensions being skewed far enough outside of what my muscle memory has adapted to over the years.

# The System76 Difference: Exceptional Machines and Support

Throughout my purchase, build and shipping phase, I was in touch with the always stellar System76 support team, who are reliably: * Responsive * Friendly * Actually helpful * Willing to go above and beyond

Every single experience I've ever had with System76 support has been outstanding.

## Conclusion: Why I Keep Choosing System76

System76 builds excellent machines with zero bullshit on them, and they provide excellent support.

This is why I am always happy to whole-heartedly recommend them.

This is not a sponsored post and nobody from System76 ever asked me to share my experiences.

## Note - this article is pretty old at this point!

Read the article

of

the

to

code

on

bug

in

is

up

> I'm thinking about learning to code. Which laptop should I get? Should I do a bootcamp? Does my child need special classes or prep in order to tackle a computer science degree?

A lot of different folks ask me if they should learn to code, if software development is a good career trajectory for them or their children, and what they need to study in school in order to be successful.

## Here's my advice in a nutshell

Before you should worry about any of that: your major, which school you're trying to get your kid into, which laptop you should purchase, you need to figure out if you (or your kid) have the "programming bug".

This will require a bit of exploration and effort on your part, but the good news is there's a ton of high quality and free resources online that will give you enough of a taste for coding and building to help you determine if this is something worth pursuing as a career or hobby. I'll share some of my favorites in this post.

## What is the programming bug?

"The programming bug" is the spark of innate curiosity that drives your learning forward. Innate meaning that it's coming from you - other people don't need to push you to do it.

In software development, coding, systems engineering, machine learning, data science; basically, in working with computers while also possibly working with people - there are periods of profound frustration and tedium, punctuated by anxiety and stress.

I have personally reached a level of frustration that brought tears to my eyes countless times. If you pursue the path of a digital craftsperson, be assured that you will, too. Especially in the beginning. That's okay.

I also happen to think that being able to describe to machines of all shapes and sizes exactly what you want them to do in their own languages; to solve problems in collaboration with machines, and to be able to bring an idea from your imagination all the way to a publicly accessible piece of software that people from around the world use and find utility or joy in - borders on magic.

## The spark of curiosity allows you to continually re-ignite your own passion for the craft

In my personal experience, considering my own career, and also the folks I've worked with professionally who have been the most effective and resilient, the single determining criterion for success is this innate curiosity and drive to continue learning and to master one's craft; curiosity in the field, in the tools, in the possibilities, in what you can build, share, learn and teach.

That's all well and good, but how do you actually get started?

## Use free resources to figure out if you have the programming bug

Don't buy a new macbook. Don't sign up for a bootcamp. Not at first.

Use the many excellent free resources on the internet that are designed to help folks try out programming in many different languages and contexts.

Here are a few that I can recommend to get you started:

[Exercism.io](https://exercism.io)

[Codewars](https://www.codewars.com/)

[Codecademy](https://www.codecademy.com/)

[Edabit](https://edabit.com/)

Give the initial exercises a shot. It doesn't really matter what language you start with first, but if you have no clue, try Python, PHP, or JavaScript. When you come across a phrase or concept you don't understand, try looking it up and reading about it.

It's key that none of these services require you to pay them anything to get started and get a taste for programming. You can do them in your browser on a weak, old computer or at the library or an internet cafe, *before* shelling out for a fancy new laptop. If it turns out you could go happily through the rest of your life without ever touching a keyboard again, you've lost nothing but a little time.

## How can you get a feel for what the work is like?

Jobs in software development vary wildly in how they look - a few parameters are company size, team size, technology stack, the industry you're in (coding for aviation is very, very different from coding for advertising in some meaningful ways), etc.

Nevertheless, it can be helpful to watch some professional developers do developer things, in order to gauge if it even seems interesting to you or not. How can you peek into the day to day of some working developers?

Luckily, plenty of developers make it easy for you to do that, by sharing content on YouTube and Twitch. This is very far from an exhaustive list, but here's a few channels I've watched recently that can help you see some on-screen action for yourself:

[Ants Are Everywhere](https://www.youtube.com/@ants_are_everywhere)

An ex

Googler reads the source code to popular open

source projects on YouTube, thinking through the process and showing how he answers his own questions as they arise. Really excellent code spelunking.

[Yours truly](https://www.youtube.com/@zackproser)

I make tutorials on open source tools as well as share some recordings of myself live

coding on some open source projects. Lately, and for the foreseeable future, I'll be going deep on A.I. applications, LLMs (large language models such as ChatGPT and others), vector databases and machine learning topics.

[TJ DeVries](https://www.youtube.com/@teej_dv)

A great open source developer, author of a very popular Neovim plugin (a coding tool for developers) and someone who makes their content accessible and interesting for all viewers.

[The Primeagen](https://www.youtube.com/@ThePrimeTimeagen)

A spicier, no

holds

barred look at all things programming, getting into coding, learning to code, and operating at a high level as a software engineer from a Netflix engineer who isn't afraid to say it like it is.

I'll continue to add more as I find good channels to help folks get a feel for the day in, day out coding tasks.

Keep in mind: these channels will give you a good taste of working with code, using a code editor and working with different languages and tools, but that's only a part of the overall job of being a professional developer.

There's entire bookshelves worth of good content on the soft skills of the job: working effectively up and down your organization, planning, team structure and dynamics, collaborative coding, team meetings, methods for planning and tracking work, methods for keeping things organized when working with other developers, etc.

These skills are perhaps even more important than the technical skills, and, if you decide this space is for you, then you'll want to ensure you're paying attention to your soft skill development as well.

## You may not might find the programming bug overnight

I've been on a computer since I was 3 years old, but for the first few years I was really only playing games, making diaramas with paint and similar programs.

Around age 11, I had a neighborhood friend who showed me an early Descent game on his PC.

He also had a C++ textbook that he let me borrow and read through. At the tender age of 11, I was thinking to myself that I would read this book, become a developer, and then make my own games. I started by trying to understand the textbook material. This didn't pan out - and it would be another 15 years until I'd make a conscious decision to learn to code.

At age 26, I joined my first tech company as a marketing associate. Luckily, there was a component of the job that was also quality assurance, and our product was very technical, so I had to use the command line to send various test payloads into our engine and verify the outputs made sense. I was hooked.

The staff-level developer who was sitting next to me gave me just the right amount of encouragement and said that if I kept at it - I would be like "this" (he made a motion with his hand of an upward ramp). From that point forward, I was teaching myself everything I could on nights and weekends. Practicing, coding, reading about coding and trying to build things. And I've never stopped.

The timeline for learning to code can be lumpy and will look different for different people. That's okay, too.

## What do you do if you think you DO have the programming bug?

So what should you do if you try out some of these types of programming exercises and you find out that you really do like them? That you find yourself thinking about them when you're doing something else?

What do you do next? **Start building and never stop**. This is the advice from a Stack Overflow developer survey from a few years ago about how to stay current and how to grow as a developer: "Build things all the time and never stop". I couldn't agree more.

The first complete web app I built for myself was my [Article Optimizer](/blog/open-sourced-article-optimizer). It was brutal. I didn't even know the names of the things I didn't know - so I couldn't Google them. I had to work backwards by examining apps that were similar enough to what I was trying to build (for example, an app that presented the user with a form they could use to submit data) and reverse engineer it, reading the page source code, and trying to find out more information about the base technologies.

Form processing, APIs, custom fonts, CSS, rendering different images based on certain conditions, text processing and sanitization. I learned a metric ton from my first major web app, even though it took me months to get it live. And the first version was thrilling, but not at all what I wanted. So I kept on refining it, re-building it. Learning new frameworks and techniques. Around the third time I rewrote it, I got it looking and functioning the way I wanted, and I got it running live on the internet so that other people could use it.

Then I maintained it as a freely available app for many years. Hosting something on the internet, on your own custom domain, will teach you a ton as well.

This is the path that worked for me: find something that's outside of your comfort zone. Figure out how to build it. Chase down every curiosity - research it as best you can and then try to get it working. Once you do, it's time for the next project. This time, do something more ambitious than last time around - something that will push you out of your comfort zone again so that you can learn even more.

## Don't pony up your cash until you've gotten a free taste

I've seen people take out a loan for $12,000 in order to complete a coding bootcamp, just to discover during their first job placement that they don't actually enjoy working on the computer all day or want to continue building digital things.

If you're currently considering learning to code or getting into computers as a possible career, don't over invest until you've given yourself a taste of coding and building.

When you're on the job site day in and day out - doing the actual work, feeling the stress, and the joy and the panic and accomplishment, Mom and Dad are not going to be leaning over your shoulder (hopefully).

Software development, hacking, designing and building systems, creating apps and sites, solving hard engineering challenges with your ever-expanding toolkit can be a wonderful career - if you enjoy doing the work.

You

need to figure out if

you

can find that spark and continually use it to renew your own interest and passion.

Looking for advice or have a question? You can subscribe to my newsletter below, and the first email in the series will allow you to reply in order to share with me any challenges you're currently facing in your career, or questions you might have.

All the best and happy coding!

like

octopus

page

on

to

win

---

## Catching a breath

I want to reflect on what I accomplished last year and what I consider my biggest wins:

I netted hundreds of new email newsletter subscribers, LinkedIn followers, and Youtube subscribers.

I open

sourced several projects, many articles and YouTube demos and tutorials that I'm proud of.

I landed a Staff Developer Advocate role at Pinecone.io, where I shipped a separate set of articles on Generative AI and machine learning, plus webinars, open

source improvements to our clients and applications, and Pinecone's first AWS Reference Architecture in Pulumi.

## The beginning of my "Year in AI"

In January 2023, I continued doing two things I had been doing for years, namely: open-sourcing side projects and tools and writing or making videos about them.

However, for some reason I felt a surge of enthusiasm around sharing my projects, perhaps because I was beginning to experiment with LLMs and realizing the productivity and support gains they could unlock.

So, I put a little extra polish into [the blog posts](/blog/) and [YouTube videos](/videos/) that shared my latest experiments with ChatGPT. Early in the year, I wrote [Can ChatGPT4 and GitHub Copilot help me produce a more complete side project more quickly? ](/blog/chatgpt-4-experiment-ai-development).

As I wrote in [maintaining this site no longer fucking sucks](/blog/maintaining-this-site-no-longer-fucking-sucks), I also re-did this site for the Nth time, this time using the latest Next.js, Tailwind and a Tailwind UI template, that I promptly hacked up to my own needs, and deployed my new site to Vercel.

Here's my commit graph for the year on [my portfolio project](https://github.com/zackproser/portfolio):

Which makes it less hard to believe that it was only 9 months ago I started building this version of the site in this incredibly hard to read screenshot of my first commit on the project:

## My blogging finally got me somewhere

Writing about what I learn and ship in tech has been an important tool for expanding my understanding and skill set for my entire career, but this was perhaps the year it most noticeably impacted my life.

In the beginning of the year I was working at Gruntwork.io, doing large scale AWS deployments for customers using Terraform, but as I wrote in [You get to keep the neural connections](you-get-to-keep-the-neural-connections), it came time for the next adventure.

And as I wrote about in [Run your own tech blog](run-your-own-tech-blog), one of the key benefits of doing a lot of writing about your open-source projects and learnings is that you have high quality work samples ever at the ready.

This year, in the middle of working an intense job as a tech lead, I managed to do some self-directed job hunting in a down market, start with 5 promising opportunities and ultimately winnow the companies I wanted to work for down to two.

I received two excellent offers in hand at the same time and was able to take my pick: [I chose to start at Pinecone as a Staff Developer Advocate](https://www.zackproser.com/blog/i-am-joining-pinecone-io).

In the break between Gruntwork.io and Pinecone.io, I took one week to experiment with Retrieval Augmented Generation and built a Michael Scott from the office chatbot.

I open-sourced the [data prep](https://github.com/zackproser/office-oracle-data-bench) and [quality testing](https://github.com/zackproser/office-oracle-db-test-notebook) Jupyter Notebooks I built for this project plus the [chatbot Next.js application](https://github.com/zackproser/office-oracle) itself, as I wrote about in my [Office Oracle](/blog/office-oracle-overview) post.

## I shipped like crazy at Pinecone

### Articles

Once I started at Pinecone, I shipped a bunch of articles on Generative AI, machine learning and thought pieces on the future of AI and development:

[Retrieval Augmented Generation](https://www.pinecone.io/learn/retrieval

augmented

generation/)

[AI Powered and built with...JavaScript? ](https://www.pinecone.io/learn/javascript

ai)

[How to use Jupyter Notebooks to do Machine Learning and AI tasks](https://www.pinecone.io/learn/jupyter

notebooks)

[The Pain and Poetry of Python](https://www.pinecone.io/blog/pain

poetry

python/)

[Making it easier to maintain open

source projects with CodiumAI and Pinecone](https://www.pinecone.io/learn/codiumai

pinecone

similar

issues/)

### Videos

[Semantic Search with TypeScript and Pinecone](https://www.youtube.com/watch?v=4Tt4LhJ6tD8)

[Live code review: Pinecone Vercel starter template and RAG

Part 1](https://www.youtube.com/watch?v=pPbTzgUV

UA)

[Live code review: Pinecone Vercel starter template and RAG

Part 2](https://www.youtube.com/watch?v=FrlG83du31Q)

[What is a Vector Database? ](https://www.youtube.com/watch?v=wc3Lh

eiNBM)

[Deploying the Pinecone AWS Reference Architecture

Part 1](https://www.youtube.com/watch?v=ySznARngHts)

[Deploying the Pinecone AWS Reference Architecture

Part 2](https://www.youtube.com/watch?v=X17MJe9OU7k)

[Deploying the Pinecone AWS Reference Architecture

Part 3](https://www.youtube.com/watch?v=ArH4H1cGfzk)

[How to destroy the Pinecone AWS Reference Architecture](https://www.youtube.com/watch?v=bxWWxKUIa3s)

[How to deploy a Jump host into the Pinecone AWS Reference Architecture](https://www.youtube.com/watch?v=gIO76I0JTnY)

### Projects

[Introducing Pinecone's AWS Reference Architecture with Pulumi](https://www.pinecone.io/blog/aws

reference

architecture/)

[Exploring Pinecone's AWS Reference Architecture](https://www.pinecone.io/learn/aws

reference

architecture/)

[GitHub: Pinecone AWS Reference Architecture with Pulumi](https://github.com/pinecone

io/aws

reference

architecture

pulumi)

## My personal writing was picked up, more than once

This was equally unexpected, thrilling and wonderful. I did not know these people or outlets, but they found something of value in what I had to say. Each of these surprised netted me a group of new newsletter and YouTube subscribers.

Daniel Messier included my rant [Maintaining this site fucking sucks](/blog/maintaining

this

site

fucking

sucks) in his Unsupervised Learning newsletter

The Changelog picked up my [Run your own tech blog post](/blog/run

your

own

tech

blog)

Habr picked up and translated my [First see if you've got the programming bug](first

see

if

youve

got

the

bug) post into Russian. This resulted in about 65 new YouTube subscribers and new readers from Russian

speaking countries.

In addition, my [programming mentor, John Arundel](https://bitfieldconsulting.com) graciously linked to my blog when he published the blog post series I lightly collaborated on with him (He did the lion's share of the work).

You can [read his excellent series, My horrible career, here](https://bitfieldconsulting.com/golang/my-horrible-career).

## The new subscribers and followers kept coming

### My site traffic saw healthy regular growth and some spikes...

As I hoped, regularly publishing a stream of new content to my site and selectively sharing some of them on social media led to more and more organic traffic and a higher count of indexed pages in top search engines.

By continuously building and sharing valuable content, tools and posts, I intend to continuously build organic traffic to my site, while eventually adding offerings like courses, training, books and more.

### EmailOctopus Newsletter cleared 200...

When I rebuilt the latest version of my portfolio site, I wired up a custom integration with EmailOctopus so that I could have total control over how my Newsletter experience looks and behaves within my site.

In a way, this is the channel I'm most excited about because it's the channel I have the most control over. These folks signed up directly to hear from me, so growing this audience is critcal for reaching my goals.

### YouTube went from 0 to over 150...

I tend to do demos and technical walkthroughs on [my YouTube channel](https://youtube.com/@zackproser). The various unexpected re-shares of my content to other networks led to a spike in YouTube subscribers.

I went from effectively no YouTube subscribers at the beginning of the year to 156 at the end of 2023.

I got a surprise hit on my video about performing GitHub pull request reviews entirely in your terminal. More evidence that you should constantly publish what you find interesting, because you never know which topic or video is going to be a hit.

### LinkedIn

LinkedIn remained the most valuable channel for sharing technical and thought leadership content with my audience.

I saw the highest engagement on this platform, consistently since the beginning of the year.

I made the subtle but important tweak of linking directly to my newsletter subscription page that appeared to immediately make an impact  in terms of signup velocity.

### Reddit

Reddit was a close second to LinkedIn, or perhaps slightly ahead of it, judging solely from referral traffic.

I found that: * longform technical tutorials tended to perform best on Reddit * the community is suspicious even when you're just giving away a tutorial or sharing something open-source * Reddit posts that do well tend to deliver steady trickles of traffic over time

## Consulting wins

I started being tapped for my insight into Generative AI, developer tooling and vector databases.

Initially, this came in the form of various think tanks and research firms asking me to join calls as an expert, and to give my opinions and share my experiences as an experienced software developer experimenting with the first raft of AI-assisted developer tooling.

Realizing the opportunity at hand, I quickly gave [my about page](/about) a face lift, making it more clear that I do limited engagements for my key areas of interest.

By the end of the year, I had successfully completed several such engagements, but was also beginning to see an uptick in direct outreach, not mediated by any third party.

## Personal wins

There were many reasons I wanted to work at Pinecone as a developer advocate. One of those many reasons was that the role involved some flying and some public speaking, both of which I have some phobia around.

I intentionally wanted to go to the places that scare me, and I am pleased to report that even after just a couple of sessions of exposure therapy this last year, I'm already feeling better about both.

I did some talks, webinars and conferences this year in Atlanta, San Francisco, New York and they all went really well, resulting in new contacts, Pinecone customers, followers and follow-up content.

## Takeaways and learnings

Publish. Publish. Publish. You cannot know in advance what will be successful and what will fall flat. Which articles will take off and which will get a few silent readers.

I am regularly surprised by how well certain posts, videos and projects do, and which aspects of them folks find interesting, and how poorly certain projects do, despite a great deal of preparation.

### Build self

sustaining loops

I use what I learn at work to write content and build side projects that people will find interesting.

I use what I learn in my side project development at work - constantly. Side projects have been an invaluable constant laboratory in which to expand my skill set and experience.

I use my skill sets and experience to help other people, including clients and those looking for assistance in development, understanding industry trends, and building better software.

Rinse and repeat constantly for many years, with minimal breaks in between.

---

I've [written before](/blog/why-ive-been-successful) about how one of the best things I ever did for my career was to hire John Arundel as a Golang mentor.

In the time that I've known John, he started out as my Golang programming coach and then became more of a general software development and career mentor.

Today, I feel fortunate to call him a friend.

As we'd have our conversations back and forth on slack and zoom calls, I'd pick his brain about advancing in one's career, finding meaningful work and, as John elegantly puts it, crafting job that you don't need a vacation from.

The more we discussed this, the more John realized there was probably a book here, and that book is now available to the world and titled, ["My Horrible Career"](https://bitfieldconsulting.com/golang/my-horrible-career).

I played a very small role in prompting him with some of the questions I was most curious to know the answers to.

John is a very talented writer, an excellent teacher and mentor and he's generously made this book free, so be sure to head over and download it now!

Download My Horrible Career

One of the best open-source tools for working with AWS that you haven't heard about is aws-vault.

https://github.com/99designs/aws

vault

Back at Gruntwork, where we did complex multi-account deployments into AWS for customers everyday, aws-vault was a standard issue tool. ## Table of contents

## aws-vault makes you more secure

Using aws-vault is the antidote to hard-coding or exposing your permanent AWS credentials like your `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.

When you run a command such as the following to list all your S3 buckets:

`aws

vault exec staging -

aws s3 ls`,

aws-vault contacts AWS's Secure Token Service (STS) in order to obtain and use temporary credentials that you can use to allow your scripts or command to interact with AWS resources securely.

**That means the command after the double dash (`--`) is not actually receiving your permanent AWS credentials, but temporary ones generated by the STS endpoint. **

This is a major boon to your overall security posture. aws-vault uses your operating systems secure keychain or equivalent secret store to keep your actual secrets safe.

If these temporary credentials get popped somehow, they expire shortly. If you hard code your keys or write them into your shell environment, you're always at risk of accidentally exposing them, which is triply bad if these are credentials to your customer's accounts.

## aws-vault makes it easier to manage multiple accounts

aws-vault helps you sanely manage multiple AWS accounts, each with its own custom alias.

This allows you to

`aws

vault exec staging -

aws s3 ls`

in order to list all your S3 buckets in your staging account. Switching to prod is then as easy as

`aws

vault exec prod -

aws s3 ls`

## Interoperate with AWS IAM credentials and IAM roles

aws-vault supports both IAM credentials (for individual IAM users) as well as IAM roles, which are preferred from a security and auditability perspective.

For example, here's an aws-vault config file that creates three profiles with a default AWS region of `us-east-1`:

This configuration allows you to easily assume different IAM roles in different AWS accounts, while still requiring multi-factor authentication for added security.

## aws

vault makes you faster

In addition to the security benefits, aws-vault makes you faster:

### Logging in to the console via aws-vault

`aws

vault exec login your

profile`

This command will open a browser window and automatically log you into the AWS Management Console for the specified profile.

### Logging into the console via aws-vault when using container tabs

Need to paste the login string into a browser tab? Perhaps because you're using separate browser tabs for each account?

Pass the -s flag to get the full string back, which you can then pipe to a copy utility (which will change depending on your platform) pbcopy (on Osx) or xsel (on Linux).

`aws

vault login

s your

profile | pbcopy`

### Quickly executing arbitrary commands via the AWS command line tool

`aws

vault exec your

profile -

aws s3 ls`

This will execute the aws s3 ls command using the temporary credentials for the specified profile. You can substitute any AWS CLI command here.

## aws-vault makes it easier to manage AWS accounts across a team

By using aws-vault, your team can securely share access to multiple AWS accounts without the risk of accidentally exposing long-lived credentials.

Each team member can have their own IAM user with appropriate permissions, and assume roles in other accounts as needed.

aws-vault's configuration files are just text, so you can share them across your team members. They are half of the equation (you still need credentials stored locally in your keychain).

That said, you can go pretty crazy with this piece if you wish. At Gruntwork, we had utilities built into our CLI tools that would auto-generate aws-vault configuration files to target AWS accounts, allowing team members to access them quickly.

"You could be the greatest architect in the world, but that won't matter much if it takes you forever to type everything into your computer."

[

**Hugo Posca

**](https://www.linkedin.com/in/hugoposca/)

---

## Why read this article? When you're finished reading this article, you'll understand the *why* and *how* behind my custom development setup, which has subjectively made me much faster and happier in my day to day work.

Here's a screenshot of my setup, captured from a streaming session.

If you're interested in developer productivity and tooling and you want to watch me hack on open source using my complete setup, be sure to check out my [my YouTube channel](https://youtube.com/@zackproser).

In this blog post, I'm going to show you exactly what I type in my terminal, and exactly how I navigate my filesystem and work on projects, using high fidelity demos:

Perhaps more importantly, this initial overview post will explain the *why* - why do I go to the trouble to learn, configure, debug and combine all these tools in exactly this way? Ultimately, my workflow reflects what I've learned so far in my career.

## Why do I care about this so much?

I believe that when it's fun and efficient to do your work and interact with your tools and devices, you're more productive and happier. Therefore, one of the reasons this topic energizes me is that it's an investment into making something that I do intensively for many hours per week as pleasurable and efficient as it reasonably should be.

But there's also another important reason I've been wanting to put out this blog post for a long time now. Many developers throughout my career have assisted me, and have taken time out of their day to stop what they were doing to show me a better way to do something, or a new tool or shortcut.

My current skill level is a product of my constant practice and the sum total of every new technique and pattern someone more experienced took the time to relay to me.

Therefore, I am also publishing this post as a means of saying thanks and paying forward the same favor to anyone who could benefit from this information.

In this post, I share the most important things I've learned so far on my journey, which is by no means complete. I am no expert, but another student.

## Learning #1 - Keep your hands on the keyboard

That's what most of this really boils down to.

In general, don't use the mouse. Browse the web using your keyboard. Yes, it will suck initially and be uncomfortable and cause you to be slower overall. This will not last long if you stick with it. I'm now much faster using Vimium to handle even semi-complex tasks like reviewing a large pull request on GitHub, because I can jump directly to the HTML nodes I want, rather than having to drag a mouse across the screen multiple times. There's a demo of me navigating GitHub with my keyboard just a bit further on in this article.

## Learning #2 - The fundamentals must be speedy

"You need to move a little faster than that son. Speed is Life." **Viper, Titanfall 2**

For a more real world and less silly example, see also Boyd's Law.

There are certain actions you'll perform a great number of times in your career as a software developer. You'll do them a number of times today, even! All of these things need to be extremely fast for you to execute. Fast like scratching an itch is - there's the impulse to be somewhere, and your fingers find the place effortlessly. **No exceptions! **

**Navigating to

** code, locally or in the browser. This means finding the correct repository and jumping into it very quickly, with minimal keystrokes.

**Understanding or mapping code

**. This means being able to see a symbol outline (variables, functions, classes, consts, etc) of a given file and see all the files in the project arranged hierarchically

**Quick pattern and string searching

** which allows you to answer the many questions that naturally arise as you're working with code

These tasks are each important enough in their own right; we'll treat each separately.

### Navigating to code, locally

I work at a company with many (> 150) repositories. I manage this by cloning all the repositories to my development machine (using a script) and optionally running another script to step into each repository and perform a git fetch and reset.

Maintaining all the repositories I'll reasonably touch locally on my machine allows me to take full advantage of powerful command line tools like `fzf` and `rg` (ripgrep).

I haven't yet felt the need to, but I could further optimize this by creating a cron job to run the update script each morning before I start work, so that I'm always looking at the latest code.

Once I started managing my code locally, fzf particularly began to shine as a tool for jumping quickly to any directory on my system. As a fuzzy-finder, fzf can do much more than that, but if you use it only for quick jumps to different directories, you'll still be deriving a great deal of value from it.

## `fzf` in action

## The default is navigation

If I run `vim` in the root of any directory, my neovim setup will automatically open the directory in neotree, which makes navigating and understanding the file system easy and fast.

## Navigating to code, in the browser

For general keyboard-based browsing, I use the Vimium plugin for Firefox. Here's a demo of me navigating an actual pull request on GitHub and demonstrating how easy (and quick) it is to:

Comment on any line

Jump to any file changed in the pull request

Expand and use even the special functions behind an ellipses menu

Start editing a file within a pull request, if desired

### Understanding or mapping code quickly

When I open a new project or file, I want to orient myself quickly and develop a general understanding of the project layout and the structure of the program. To accomplish this, I lean on AstroNvim's configuration to pop up a symbol outline in my current file that I can use to navigate and understand the code:

### Finding files, code or arbitrary strings on your local machine

Whether you're working on a new feature, trying to orient yourself to a new codebase or performing upgrades across several repositories, you're naturally going to have a lot of questions about your source code come up:

Which versions of this software are currently deployed?

How many places does that string we need to replace occur?

Did that same typo we've seen elsewhere occur in this code?

And many, many more. You'd be surprised how many of these questions ripgrep can answer for you. I recommend learning as many of the flags for `rg` as you can. I picked up ripgrep a few years ago and it remains one of the things I run constantly throughout the day to get my work done more efficiently.

## Learning #3 - Reflow your workspace to fit your work I may execute many different tasks during the course of a day: write a new feature in one language, fix up some flaky or slow tests in another, write an RFC in markdown, create new configuration files, perform a deployment, etc.

This involves getting data from one place like a `terraform output` into a command line argument, and then copying and pasting the output of that command into the separate log file you're keeping, which you then separately want to pipe into another operation in a different pane. I think of my tmux panes as unix pipes.

The main idea is that my workspace is a fluid thing that can shift and scale up or down in order to accomodate the task at hand. If I'm writing code that needs to know the docker image ID of a particular image I built recently, I can pop open a new tmux pane and run whatever Docker commands I need to get that information. Because each pane is a shell, I can script against and further process my output with any and every unix tool to get exactly what I want.

Let's make this more concrete with an example. In the following demo gif, I use neovim to edit code, which means my editor is just another tmux pane. In this case, I'm writing some code that needs a Docker image ID. I need only create a new split and do my Docker work there. When I have the Docker image ID I need, I can close the extra pane, recovering the full screen's real estate for my focused coding task.

In my day to day work, I might have between 3 and 8 different terminal panes open on a single screen, depending on the task. Panes show up to do some work and get some output that can be easily piped to any other pane. Panes whose work is finished can get closed down, recovering screen real-estate for other tasks. I constantly reflow my workspace to my work.

## Desktops - with an s Awesome Window Manager allows me to organize my work across two external monitors into 9 Windows each. This is extremely handy and something I use everyday.

Here's a rough idea of how I divide up my Windows to provide you with some inspiration, but everyone is likely going to settle on an arrangement that makes them happy:

1. Comms (Slack, email) 2. Notes / Second brain (Obsidian.md) 3. Spotify 4. Zoom call 5. Main tmux window for editing code, with all sessions stored as tmux sessions 6, 7, and 8 are my utility windows. I currently run my StreamDeck UI and logs across these 9. Browser windows for whatever I'm actively working on

Having these windows divided up in this way simplifies context-switching throughout the day for me. I always know exactly which window has which kind of application running in it, so it's snappy and natural to switch back and forth between them as needed, even while pair-coding or on a Zoom call.

# Next up, and a request

That's it for this introductory post! In forthcoming posts in this series, I'll go deep on:

setting up these tools

how to install and configure them

managing your configuration with git for recovery and reuse across multiple machines

shell optimizations that compound the speed boosts

advanced patterns, custom shell functions, additional use

cases, demos and useful scripts

And now, a humble request. If you've found this article at all helpful or interesting, please share it with someone you think could benefit from the information. And, if you have feedback, questions or other content you'd like to see in the future, please don't hesitate to reach out and let me know. Thank you for reading!

## Overview At [Gruntwork](https://gruntwork.io), we use CircleCI as the continuous integration tool for the vast majority of our repositories. This article shares a couple of tips and tricks I've figured out along the way for more easily working with CircleCI locally, since the test cycles in the cloud can be pretty long, depending on what you're using CircleCI for.

## Recommended tools If you don't know, CircleCI maintains a [handy command line interface (CLI) tool](https://circleci.com/docs/2.0/local-cli/), which has a couple of handy features. If you use CircleCI regularly, installing this tool is worthwhile.

# Verifying your .circleci/config.yml locally Prior to pushing your modified config and waiting for test errors to come back, which can take time depending on the repo and test setup, it's a good idea to smoke test the validity of your config locally first to catch any YAML syntax errors or CircleCI-specific errors:

You want to see the following output prior to pushing your changes:

## Testing YAML anchors and aliases Working with CircleCI means working with YAML and its various features, such as [anchors and aliases](https://circleci.com/blog/circleci-hacks-reuse-yaml-in-your-circleci-config-with-yaml/). Sometimes, you'll want to verify the rendered YAML to ensure your usage of anchors and aliases, for example, are working as intended.

You can do that with the following command, assuming that you are iterating on your local working copy of `.circleci/config.yml`:

In the above command, the final `-` means STDOUT, so you can get the final rendered output of your .circleci/config.yml, with all aliases fully expanded, in your terminal. If you wanted to instead write the rendered output to a different yaml file you could do so by instead providing an output filename:

This is a great way to verify, for example, that your changes to an environment variable mapping are being rendered as you expect.

bladerunner

many

bladerunner

enraged

at

inlined

ai

forgetting

its

ai

dev

tool

quitting

your

crappy

 A confused hacker trying to figure out why the AI-assisted IDE Cursor just broke his entire file by inlining its generated code in the wrong place

## Inlining response code in the wrong place.

Cursor, I'm looking at you. If I select a block of code in my IDE, then pop up the cursor prompt with command+k and ask for a reasonable unit test to be generated, then, believe it or not, I don't want that unit test generated directly inside my application code, breaking my entire file as it sprays itself across functions and comments.

This is a symptom of the follow-up problem, where an agent or AI in a complex environment hasn't been sufficiently prompted to determine when it needs to ask follow-up questions in order to perform the task correctly.

Unfortunately, the first time I tried out the AI-assisted Cursor IDE, and asked for a unit test, the unit test was inlined directly in my application code without a moment's pause or second thought.

Ideally, the first time I ask for a unit test to be generated, the agent should: * realize it needs to ask me follow-up questions such as where do I want the test files to live * offer me a few options from some sane defaults for my project, language and framework * offer to remember my choice in the future so that I don't continuously have to repeat myself

## "Forgetting" its original prompting after long breaks in coding sessions

I've noticed this to be most pronounced in Sourcegraph's Cody tool, which is otherwise excellent and, I feel, one of the more promising AI-assisted developer tools.

If you are working with Cody for a while, asking questions about your codebase, having it generate boilerplate code for you, and then you take a multi-hour break to do something else, when you return and ask it another question about your code, Cody will tend to respond that it's just an AI model with no access to your codebase.

"Huh, but you are an AI model with direct access to the graph representation of my code as well as vector embeddings of all my files and you've been specifically prompted to read my code whenever I issue you a natural language query about it, my good AI assistant! ", I'll remind it.

More often than not, this reminder is sufficient to help rattle Cody back into reality and it continues to hum along more or less correctly after that. This issue is not specific to cody.

Even after hours of happily coding alongside ChatGPT4 in the browser on a specific application together, I'll occasionally get back odd responses that suggest ChatGPT4 has temporarily "forgotten" what we're focusing on, discarding all the prior context in the same session.

At this point, a similar strategy of reminding it what we're doing, and being extremely explicit about what I want it to return when it responds, tends to put it back on track.

## Hand-waving away the critical parts of generated code snippets

When I code alongside ChatGPT4, I will occasionally ask it to generate the beginnings of either a new component for me, if I'm working in Next.js, or an API route, or perhaps a Jest unit test. Most of the time, ChatGPT4 is surprisingly effective at what it can generate and explain clearly.

However, I've noticed that the longer or deeper I go into a given session with it, the more likely it becomes to start omittting for brevity the specific code that I'm looking to see generated, either because I'm experiencing a bug or because I don't understand how some feature or concept that is new to me is supposed to work.

This manifests as ChatGPT4 returning an otherwise high quality response, yet eliding the specific code or parameters I asked it about with:

To which I then respond: "Do not elide code by stating - the rest of your code here. Expand the sample code fully. Omit your explanation if that is required to return your entire response within the text length or time limits."

This is mostly a minor annoyance, and, to be fair, as of this writing, I still find ChatGPT4, either in a browser tab or via elegant open-source wrappers such as [`mods`](https://github.com/charmbracelet/mods) to be the most effective coding and architectural discussion partner of all the tools I've tried this year.

## Having poor setup UX

This is sort of par for the course with Neovim plugins, and tends to be considered a feature, more than a bug, because Neovim users are somewhat inured to painful setups. After all, we volunteer to install a plugin from a stranger's GitHub respository and configure it with various different styles of lua snippets depending on which plugin manager (of several options) we happen to be using. I'm not claiming we're all sadists, but let's say getting a new plugin installed and configured to our liking requires grit, and we expect this.

VSCode is a completely different animal. It feels more like an operating system than an IDE, and perhaps the thing that annoys me the most about it is the number of popups and third-party extension alerts that are thrown up on screen every time I simply load a file to try to read or edit it.

The benefit of having a, shall we say, "richer" UX is that the installation and configuration workflows for VSCode can be more complex. Many plugins, especially AI-assisted developer tools, tend to first show a popup asking you to authenticate to, say Codeium, and provide a link that opens in your browser and asks you to authenticate to Codeium's servers.

Of course you then need to authorize the return - wherein Codeium's login flow asks you for permission to open a link in your VSCode IDE in order for your IDE to capture the auth token generated that identiifes your account and save it to the known path where it will be loaded from in the future.

There's plenty of hops here and plenty of things that can go wrong, usually on the user's system or browser permissions side, so this all tends to be clunkier than I'd ideally like to begin with. More than once, on different operating systems and machines, I've noticed needing to re-authenticate to Codeium or Cody in VSCode despite already having done so in my previous session, which is annoying, and does not inspire confidence.

But this isn't the real issue. The real issue is that, even once you're through all this auth configuration round-tripping, and your token is actually saved where it's expected, most companies making AI-assisted developer tools right now do not have a sufficient onboarding experience that walks you through: 1. How to use the tool for even basic flows that are advertised on the marketing website 1. How to configure the tool to behave in different ways, if it supports that 1. What to do if it doesn't appear to be working correctly

Just about every single tool I've evaluated comes up short in this regard, leading to a lackluster experience loop that tends to go like this:

1. Spend time browsing the tool's marketing site after reading about it on hacker news or getting a recommendation from a friend 1. Get mildly excited based on the features and capabilities the site claims the tool has 1. Spend a somewhat unreasonable amount of time just getting the tool installed and configured to run properly in your IDE, whether that's Neovim or VSCode 1. Attempt to do the "hello world" version of what the tool says it can do - such as generate unit tests for your codebase 1. Get either no output, the wrong output or output that looks plausible but does not run correctly 1. Find nothing in the way of troubleshooting or follow-up information 1. Realize you've been tricked and get irritated 1. Uninstall the tool immediately and remind yourself to be more skeptical of this company's claims and tooling going forward

## Not testing its own output or having an easy way to send a question and response chain back to developers

This is a tremendous miss that I see most AI-assisted developer tooling startups making right now. With no way to get automated or one-click reports sent home about which flows are not working for their end users, most companies building intelligent developer tooling are truly flying blind to how poorly their product is working in the wild on their prospective customers' real files and codebases.

To wit, I recently tested out a new and up and coming AI dev tooling company's flagship offering within VSCode, and started a Google Doc to capture any UX or UI feedback I thought I might have while trying it out. Unfortunately, by the end of my hour long session using the tool, I had filled 9 pages of the Google Doc with screenshots, stack-traces and comments explaining how the tool's flagship feature would not even load properly, in some cases, and how it produced output that was broken or mis-configured for my codebase, in every other instance.

Despite being a staff-level developer and having tons of experience with different developer tools, Neovim plugins, raw Vim plugins, VSCode, Sublime text, and despite having an above average tolerance for pain and suffering, I could not get their primary tool to work as advertised on the box, and I wasn't even providing it with a tricky codebase as a curveball.

I say this to hammer home the point that if you're not instrumenting your dev tools sufficiently to capture stacktrackes, anonymized logs, or at least providing your end users with a "click to send stacktrace" button that allows them to optionally provide additional context into what they were doing when your tool failed them, you're not going to have end users for very long.

This will most likely manifest as your startup being slow to find adoption, and you'll continue to beg, borrow and steal developers to try out your offerings, only to never hear back, get terse non-committal answers about your tool not being for them, or, most likely silence. What that silence really means is that they tried installing and configuring your tool, if they even got that far, it failed spectacularly for them a few times in a row which led to them cursing loudly multiple times and then uninstalling your tool in a fit of rage. No, they're not coming back. Not even when you release version 2.0 with that fancy new feature.

If developers cannot successfully use your tool in the first place, then they cannot evaluate whether or not it would make their lives easier or richer in the long term. If they cannot get it to work most of the time, they won't leave it installed. If they don't leave it installed, they won't reach that point of nirvana where your tool becomes a delightful, trusted and essential part of their workflow, at which moment they'd happily consider sending you money in order to continue using it.

But most AI devtool startups I've evaluated over the past year continue to ignore getting step 0 rock solid at their own peril, and attempt to sprint before they can crawl. They're leaving a trail of silently frustrated developers in their wake, and most of them have better things to do than to write you up detailed bug reports about exactly where your offering is falling short. That silence you're getting back on your end should be a very loud alarm.

so

## How do I write so fast?

Occasionally someone will ask me how I am able to write new content so quickly. This is my answer.

There are two reasons I'm able to write quickly:

## 1. I write in my head

I mostly write new articles in my head while I'm walking around doing other things. This means that by the time I am back at a computer, I usually just need to type in what I've already hashed out.

## 2. I automate and improve my process constantly

The fact that I'm constantly writing in my own head means that my real job is rapid, frictionless capture.

When I have an idea that I want to develop into a full post, I will capture it in one of two ways:

1. I'll use Obsidian, my second brain, and add a new note to my `Writing > In progress` folder

1. I'll use my own tool, Panthalia, ([intro](https://zackproser.com/videos/panthalia-intro) and [update](https://zackproser.com/videos/panthalia-speech-to-text)), which allows me to go from idea fragment to open pull request in seconds

I've found there's a significant motivational benefit to ***only needing to finish an open pull request*** versus writing an entire article from scratch.

Going from post fragment (or, the kernel of the idea) to open pull request reduces the perceived lift of the task.

---

In this tutorial, I walk readers through how the Pinecone AWS Reference Architecture's autoscaling policies work, and how to use the tools provided in the repository to generate test records to flex the system under load.

This is the eigth article I wrote while working at Pinecone:

Read article

----

## Demo

## Intro

have you ever needed to add a particular file across many repos at once? Or to run a search and replace to change your company or product name across 150 repos with one command? What about upgrading Terraform modules to all use the latest syntax? How about adding a CI/CD configuration file, if it doesn’t already exist, or modifying it in place if it does, but only on a subset of repositories you select?

You can handle these use cases and many more with a single git-xargs command. Just to give you a taste, here’s how you can use git-xargs to add a new file to every repository in your Github organization:

In this example, every repo in the my-example-org GitHub org have a CONTRIBUTIONS.txt file added, and an easy to read report will be printed to STDOUT :

## Try it out

git-xargs is free and open-source - so you can grab it here: [https://github.com/gruntwork-io/git-xargs](https://github.com/gruntwork-io/git-xargs)

## Learn more

Read [the introductory blog post](https://blog.gruntwork.io/introducing-git-xargs-an-open-source-tool-to-update-multiple-github-repos-753f9f3675ec) to better understand what git-xargs can do and its genesis story.

---

## Introduction

[Pageripper is a commercial API](https://rapidapi.com/zackproser/api/pageripper/) that extracts data from webpages, even if they're rendered with Javascript. Pageripper extracts: * links * email addresses * Twitter handles

from any public website.

## Who is it for?

Pageripper is a tool for data analysts, digital marketers, web developers and more who need to reliably retrieve links from websites.

Pageripper is often ideal for folks who could write their own data extraction API themselves, but don't want the hassle of hosting or maintaining it.

## A simple example

Let's look at an example. Suppose we want to extract all the links from the New York times homepage:

We'd then get back:

You can optionally request email addresses and Twitter handles, too. And you can specify which network event Puppeteer should wait for when fetching a site to extract data from.

## What problem is Pageripper solving?

As Javascript-heavy websites have become increasingly popular over the years, we've seen a sharp rise in Single Page Applications (SPAs). These sites behave differently from traditional dynamic websites that might be rendered by the backend. In the name of increased performance and optimizing for the end user experience, SPAs send a single minimal HTML file that links to a Javascript bundle.

This Javascript bundle contains the entirety of the frontend application's logic for rendering the UI, including all links. While this approach vastly improves loading times and reducing the overall time until a given site is interactive for the end user, it complicates data-extraction tasks such as getting all the links out of a given page.

For more detail and a demonstration of what scrapers see under the hood when fetching these two different site architectures, see [Evolving web scraping: How Pageripper handles Javascript-heavy sites](/blog/evolving-web-scraping-pageripper-api).

The Pageripper API addresses this challenge by using Chrome's Pupeteer project under the hood. This means that the API renders sites in exactly the same way a fully fledged web browser, such as Chrome, would.

This enables extraction of data such as links, email addresses and Twitter handles, even from sites that are rendered entirely in Javascript.

Pageripper lets you tweak Pupeteer's behavior on a per-request basis in order to specify which network events to wait for. This additional control allows users to get the best data-extraction experience from a much wider array of sites on the public internet.

## Scalability and performance

Pageripper is a Node.js project written in TypeScript that is Dockerized into a container. The container includes Puppeteer and a headless Chrome browser as a dependency, which it uses to render websites.

The API is defined as an ECS service for scalability and deployed on AWS behind a load balancer.

Pageripper is defined via Infrastructure as Code (IaC) with Pulumi. Additional capacity can be added quickly in order to meet demand.

## Key features and benefits

Pageripper allows you to fetch and extract data from any public website. On a per-request basis, you can even pass options to tweak and tune Puppeteer's own behavior, such as which network event to wait for when fetching a site to extract data from.

## Use cases

Pageripper is ideal for getting usable data out of large pages. For example, a conference site might publish the contact information and company websites for speakers at an upcoming event that you're attending. Pageripper is ideal for fetching this data from the page and making it available to you easily so that you can process it further according to your needs.

## Integration and usage

[Pageripper is currently available via RapidAPI](https://rapidapi.com/zackproser/api/pageripper/). The [API's OpenAPI spec](https://github.com/zackproser/pageripper-v2/blob/main/spec/swagger.yml) is available in the public GitHub repository.

You can test out the API from RapidAPI if you're curious to kick the tires. In the future, there will likely be client SDKs generated for various languages to ease integration.

## Pricing and Commercial uses

Pageripper is available for commercial use. There are currently [three pricing plans](https://rapidapi.com/zackproser/api/pageripper/pricing) to support different needs and scaling requirements.

## Getting started

[RapidAPI page](https://rapidapi.com/zackproser/api/pageripper) (test, sign

up for, and get API keys to use the API)

[API Documentation](https://zackproser.github.io/pageripper

v2/)

Please give Pageripper a shot and let me know what you think!

---

## Table of contents

## Understanding Dimensionality in Vectors: A Comprehensive Guide 📈

In the realm of machine learning and data science, [vectors](/blog/introduction-to-embeddings) play a crucial role in representing and processing data.

One fundamental concept related to vectors is dimensionality. In this article, we'll explore what dimensionality means, provide simple examples to demonstrate its significance, and discuss its importance in the context of vector databases and machine learning models. ## What is Dimensionality? 🌍

Dimensionality refers to the number of components or features that make up a vector. In other words, it represents the size or length of a vector. Each component of a vector corresponds to a specific attribute or variable in the data.

For example, let's consider a vector representing a person's characteristics:

`[age, height, weight]`

In this case, the vector has a dimensionality of 3, as it consists of three components: age, height, and weight.

## Simple Examples 🎨

To further illustrate the concept of dimensionality, let's look at a few more examples:

### A vector representing the coordinates of a point in a 2D plane:

`[x, y]`

This vector has a dimensionality of 2.

### A vector representing the RGB color values of a pixel:

`[red, green, blue]`

This vector has a dimensionality of 3.

### A vector representing the features of a text document:

`[word_count, sentiment_score, topic_relevance]`

This vector has a dimensionality of 3.

As you can see, the dimensionality of a vector depends on the number of attributes or features it represents.

## Importance of Dimensionality

Dimensionality plays a crucial role in vector databases and machine learning models. Here are a few reasons why:

Vector Similarity: Vector databases often rely on similarity measures, such as cosine similarity or Euclidean distance, to compare and retrieve similar vectors. The dimensionality of the vectors directly affects the accuracy and efficiency of these similarity calculations.

Model Compatibility: Machine learning models, such as neural networks, expect input vectors to have a specific dimensionality. Mismatching the dimensionality of input vectors with the model's expected input shape can lead to errors or incorrect results.

Computational Complexity: The dimensionality of vectors impacts the computational complexity of operations performed on them. Higher

dimensional vectors require more memory and computational resources, which can affect the performance and scalability of vector

based systems.

## Dimensionality Reduction Techniques in Machine Learning

In machine learning, managing the dimensionality of data is crucial for optimizing model performance. High-dimensional data can overwhelm models, making them slow and less interpretable. To counter this, dimensionality reduction techniques are employed. These include:

Feature Selection: Identifying and retaining only the most relevant features, discarding the rest.

Matrix Factorization: Techniques like Principal Component Analysis (PCA) break down data into its constituent parts, keeping only the most significant ones.

Manifold Learning: Projects high

dimensional data into a lower

dimensional space, preserving essential structures or relationships.

Autoencoders: A type of neural network that learns a compressed, dense representation of the input data.

Each technique has its applications, strengths, and considerations. Integrating these into your data preprocessing pipeline can lead to more efficient and interpretable models, significantly impacting outcomes in machine learning projects.

## Mismatching Dimensions

When working with vector databases or machine learning models, it's crucial to ensure that the dimensionality of vectors matches the expected input shape. Mismatching dimensions can lead to various issues:

Incompatibility: If the dimensionality of input vectors doesn't match the expected input shape of a model, it will raise an error or fail to process the data correctly.

Incorrect Results: Even if a model manages to handle mismatched dimensions, the results may be incorrect or meaningless. The model might make predictions based on incomplete or irrelevant information.

Performance Degradation: Mismatched dimensions can lead to inefficient memory usage and increased computational overhead, resulting in slower performance and reduced scalability.

To avoid these issues, it's essential to preprocess and align the dimensionality of vectors before feeding them into vector databases or machine learning models.

## Generating Sample Vectors in Python 🐍

Python provides various libraries and tools for generating sample vectors of arbitrary length. Here's an example using the NumPy library:

Output:

`[0.64589411 0.43758721 0.891773   0.96366276 0.38344152]`

You can customize the length of the vector by modifying the argument passed to np.random.rand(). Additionally, you can generate vectors with specific distributions or patterns using other NumPy functions like np.zeros(), np.ones(), or np.linspace().

## Wrapping up

Dimensionality is a fundamental concept in the world of vectors and plays a vital role in vector databases and machine learning models. Understanding what dimensionality means, its importance, and the consequences of mismatching dimensions is crucial for effectively working with vector-based systems.

routine

routine

routine

---

## My morning routine

One of my productivity "hacks" is to get up several hours before work starts and go into the office. I've been doing this since my last job - roughly four years now.

Here's what I tend to do:

## Hack on things that are important to me

The longer the day goes on, the more obligations and competing responsibilities I have tugging on me. Setting this time aside allows me to give some focus to projects I wouldn't otherwise get to ship or learn from.

## Write

I'll write blog posts and newsletters. I'll write to old friends whom I haven't spoken to in a while or to new connections I just met. I'll write notes about my ideas so they're properly captured for later. I'll return emails if they're important.

## Write more

Sometimes, I'll write out my thoughts in a raw format if I'm trying to think through or process something personal or professional. I prefer to use Obsidian for my second brain because the sync functionality works so well across my development machines and phone.

## In the past, I drew

I used to alternate pretty well between art and computer stuff, but lately, computer stuff has been winning out for several reasons. Luckily, I can use computers to generate imagery, which I quite enjoy. But doing art in the morning can be good.

I'll say that it CAN BE good because regardless of the medium I'm working in during my morning sessions, I'll either get a huge rush and boost from getting something I like done before work or go into work feeling pretty frustrated that I was spinning my wheels on something.

## Clean up my office

And my desk and floor. Take out the trash. Restock something that's out. Ensure I have water, snacks, caffeine, tools, a notebook, and pens. Open the window, turn on the fan - wipe down my desk. Remove the keys that are sticking and clean them.

## Read

Outside of showing up to work every single day and trying to learn something or improve, this habit has probably had the greatest total impact on my life and career. I'll read technical books and business books within and without my comfort zone, and I'll read about health, the body, or the personal habits of famous artists and writers.

I went a little deeper into reading and why it helped me so much in [this post](/blog/why-ive-been-successful), but I'm due to create an updated reading list on a blog post somewhere. Start now if you're not reading a lot and want to boost your career.

## Meditate

I have to return to this one constantly and restart repeatedly (which is okay). But it greatly helps my general outlook, mood, and focus.

## Reflect

As I get older, I try to focus on my inputs more - the regularity of showing up every day despite whatever insults or injuries have me moving slowly when I first wake up. More of the creating and publishing and less of the checking analytics...

But I'm human. Having a crappy start to the day development-wise still bums me out. Fortunately, I am energized most of the time by whatever I am working on that morning.

What's your morning routine? Let me know in the comments below 👇.

---

At the time of this writing, there **are** [official docs on building a sitemap for your Next.js project](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap) that uses the newer app router pattern.

However, they're on the sparse side, and there's more than a little confusion going around about how to pull this off based on the npm packages and GitHub issues I encountered when doing my own research.

When you're finished this post, you'll have the knowledge and code you need to build a dynamic sitemap for your Next.js project that includes both 'static' routes such as `/about` or `/home` and dynamic routes such as `/blog/[id]/page.tsx` or `/posts/[slug]/page.js`.

You can also feel free to skip ahead to the implementation below. This is what I used to get a working sitemap that included all the routes I cared about and, more importantly, was accepted by Google Search's console / robots:

## Key things to understand

Here's the things I needed to know upfront when I wanted to build my own sitemap:

### The Next.js file to route convention is supported

You can use the file-convention approach, meaning that a file that lives at `src/app/sitemap. (js|ts)`

Your file should export a default function - conventionally named `sitemap`. I've demonstrated this in the implementation example below.

### You can test your sitemap locally To test your sitemap, you can use curl like `curl http://localhost:3000/sitemap.xml > /tmp/sitemap.xml` and I recommend redirecting the output to a file as shown so that you can examine it more closely.

At a minimum, all of the URLs you want search engines and other readers of sitemaps to be aware of need to be present in this file and wrapped in `` or location tags.

If your sitemap is missing key URLs you're looking to get indexed, there's something wrong in your logic causing your routes to be excluded.

You can do print debugging and add logging statments throughout you sitemap file so that when you're running `npm run dev` and you curl your sitemap, you can see the output in your terminal to help you diagnose what's going on.

## Next.js dynamic and static sitemap.xml example

The following implementation creates a sitemap out of [my portfolio project](https://github.com/zackproser/portfolio).

It works by defining the root directory of the project (`src/app`), and then reading all the directories it finds there, applying the following rules:

1. If the sub-directory is named `blog` or `videos`, it is a dynamic route, which needs to be recursively read for individual posts 2. If the sub-directory is not named `blog` or `videos`, it is a static or top-level route, and processing can stop after obtaining the entry name. This will be the case for any top-level pages such as `/photos` or `/subscribe`, for example.

Note that if you're copy-pasting this into your own file, you need to update: 1. your `baseUrl` variable, since it's currently pointing to my domain 2. the names of your dynamic directories. For my current site version, I only have `/blog` and `/videos` set up as dynamic routes 3. Your excludeDirs. I added this because I didn't want my API routes that support backend functionality in my sitemap

That's it! Save this code to your `src/app/sitemap.js` file and test it as described above.

I hope this was helpful, and if it was, please consider subscribing to my newsletter below! 🙇

three

walls

with

one

three

walls

findable

for

your

montage

montage

montage

your

 Without much more work, you can ensure your side projects are not only expanding your knowledge, but also expanding your portfolio of hire-able skills.

Building side projects is my favorite way to keep my skills sharp and invest in my knowledge portfolio. But this post is about more than picking good side projects that will stretch your current knowledge and help you stay up on different modes of development. It's also about creating a virtual cycle that goes from:

Idea

Building in public

Sharing progress and thoughts

Incorporating works into your portfolio

Releasing and repeating

It's about creating leverage even while learning a new technology or ramping up on a new paradigm and always keeping yourself in a position to seek out your next opportunity.

## Having skills is not sufficient

You also need to display those skills, display some level of social proof around those skills, and, importantly, be findable as a person with said skills.

 It's one thing to actually complete good and interesting work, but does it really exist if people can't find it? You already did the work and learning - now, be find-able for it.

In order to best capture the value generated by your learning, it helps to [run your own tech blog](/blog/run-your-own-tech-blog) as I recently wrote about.

Let's try a real world example to make this advice concrete. Last year, I found myself with the following desires while concentrating on Golang and wanting to start a new side project:

I wanted to deepen my understanding of how size measurements for various data types works

I wanted a tool that could help me learn the comparative sizes of different pieces of data while I worked

I wanted to practice Golang

I wanted to practice setting up CI/CD on GitHub

How could I design a side project that would incorporate all of these threads?

## Give yourself good homework

Simple enough: I would build [a Golang CLI that helps you understand visually the relative size of things in bits and bytes](https://github.com/zackproser/sizeof).

I would build it as an open-source project on GitHub, and write excellent unit tests that I could wire up to run via GitHub actions on every pull request and branch push. This would not only give me valuable practice in setting up CI/CD for another project, but because my tests would be run automatically on branch pushes and opened pull requests, maintenance for the project would become much easier:

Even folks who had never used the tool before would be able to understand in a few minutes if their pull request failed any tests or not.

I would keep mental or written notes about what I learned while working on this project, what I would improve and what I might have done differently. These are seeds for the blog post I would ultimately write about the project.

Finally I would add the project to [my `/projects` page](/projects) as a means of displaying these skills.

## You never know what's going to be a hit

In art as in building knowledge portfolios, how you feel about the work, the subject matter and your ultimate solutions may be very different from how folks who are looking to hire or work with people like you may look at them.

This means a couple of things: it's possible for you to think a project is stupid or simple or gross, and yet have the market validate a strong desire for what you're doing. It's possible for something you considered trivial, such as setting up CI/CD for this particular language in this way for the 195th time, to be exactly what your next client is looking to hire you for.

It's possible for something you consider unfinished, unpolished or not very good to be the hook that sufficiently impresses someone looking for folks who know the tech stack or specific technology you're working with.

It's possible for folks to hire you for something that deep down you no longer feel particularly fired up about - something stable or boring or "old hat" that's valuable regardless, which you end up doing for longer to get some cash, make a new connection or establish a new client relationship.

This means it's also unlikely to be a great use of your time to obsess endlessly about one particular piece of your project - in the end, it could be that nobody in the world cares or shares your vision about how the CLI or the graphics rendering engine works and is unique, but that your custom build system you hacked up in Bash and Docker is potentially transformative for someone else's business if applied by a trusted partner or consultant.

## Release your work and then let go of it

Releasing means pushing the big red scary button to make something public: whether that means merging the pull request to put your post up, sharing it to LinkedIn or elsewhere, switching your GitHub repository from private or public, or making the video or giving the talk.

Letting go of it means something different. I've noticed that I tend to do well with the releasing part, which makes my work public and available to the world, but then I tend to spend too much time checking stats, analytics, click-through rates, etc once the work has been out for a while. I want to change this habit up, because I'd rather spend that time and energy learning or working on the next project.

Depending on who you are and where you are in your creative journey, you may find different elements of this phase difficult or easy. My recommendation is to actually publish your work, even if it's mostly there and not 100% polished. You never know what feedback you'll get or connection you'll make by simply sharing your work and allowing it to be out in the world.

Then, I recommend, while noting that I'm still working on this piece myself, that you let it go so that you are clear to begin work on the next thing.

## Wash three walls with one bucket

The excitement to learn and expand your skillset draws you forward into the next project.

The next project gives you ample opportunity to encounter issues, problems, bugs, your own knowledge gaps and broken workflows. These are valuable and part of the process; they are not indications of failure;

Getting close enough to the original goal for your project allows you to move into the polishing phase and to consider your activities retrospectively. What worked and what didn't? Why? What did I learn?

Writing about or making videos about the project allows you to get things clear enough in your head to tell a story - which further draws in your potential audience and solidifes your expertise as a developer.

Your finished writing and other artifacts, when shared with the world, may continue to drive traffic to your site and projects for years to come, giving you leads and the opportunity to apply the skills you've been honing in a professional or community context.

Create virtuous cycles that draw you forward toward your goals, and wash three walls with one bucket.

## Where did this phrase come from?

"Kill two birds with one stone" is a popular catchphrase meaning to solve two problems with one effort. But it's a bit on the nose and it endorses avicide, which I'm generally against.

One of my professors once related the story of one of her professors who was a Catholic monk and an expert in the Latin language. He would say "Bullshit! ", it's "wash two walls with one bucket" when asked for the equivalent to "kill two birds with one stone" in Latin. I liked that better so I started using it where previously I would have suggested wasting a pair of birds.

For this piece, I decided the key idea was to pack in dense learning opportunities across channels as part of your usual habit of exploring the space and practicing skills via side projects. So, I decided to add another wall.

codeium

context

module

 Codeium is still my favorite AI-assisted autocomplete tool. Here's a look at why, and at the company behind it.

## Table of contents

## Reviewing Codeium almost a year later

I initially reviewed Codeium [here](https://zackproser.com/blog/codeium-review), almost one year ago, when it became my daily-driver code completion tool.

Now, I’m revisiting Codeium. I’ll explain why I still prefer Codeium over other tools and what improvements the Codeium team has made in the past year.

### How I code and what I work on

While I have VSCode and Cursor installed and will occasionally jump between IDEs, for the most part I code in Neovim, and run the [AstroNvim](https://astronvim.com/) project.

 AstroNvim is an open-source project that composes Neovim plugins together using a Lua API to create a full fledged IDE experience in your terminal. And Codeium's Neovim integration works well here.

[AstroNvim](https://astronvim.com/) is an open-source community-maintained configuration of Neovim plugins and associated config to turn stock Neovim into an IDE similar to VSCode.

These days, I primarily work with the Next.js framework, and ship JavaScript, Typescript, Python, and more while working on full stack applications, data science Jupyter Notebooks, and large scale distributed systems defined via Infrastructure as Code.

 Codeium's dashboard keeps track of your completions and streaks.

## Why Codeium is Still My Favorite Tool

### High

quality code completion

Codeium offers precise, contextually appropriate code completion suggestions.

This feature speeds up the development process and reduces errors, making coding more efficient.

### **Proprietary context-aware model leads to higher accuracy**

Codeium’s proprietary model ([ContextModule](https://codeium.com/blog/context

aware

everything

more

advanced

realtime

context

than

github

copilot)) takes into account:

1. The list of files you currently have open 2. The repo-wide context of every file in your project

In addition to ContextModule, designed to determine the most relevant inputs and state to present to the LLM, Codeium employs reranking and prompt building to achieve high-quality suggestions.

Reranking uses Codeium secret sauce and precomputed embeddings (vector representations of natural language and code) to determine the relative importance of contextual snippets that **could** be fed to the LLM.

Finally, in the prompt building step, Codeium supplies carefully crafted prompts alongside the reranked context, ensuring highly accurate code completion suggestions.

### Widest support for different file types

A standout feature of Codeium is its versatility. It seamlessly works across various file types—from Dockerfiles to application code and even prose, providing support wherever needed.

At the time of writing, [Codeium supports over **seventy** different file types, whereas GitHub Copilot supports far fewer. ](https://codeium.com/compare/comparison-copilot-codeium)

### *

Support for Neovim**

Codeium's early support for [Neovim](https://github.com/neovim/neovim) is noteworthy, because most AI-assisted developer tooling entrants initially introduce support for VSCode only.

[Neovim](https://github.com/neovim/neovim) users often find that AI-assisted tools lag in compatibility. Still, Codeium's integration here is robust and effective, enhancing the development experience for a broader audience and winning the Codeium team some hacker street-cred.

## Codeium

Key Features and Updates

### *

AI

Powered Autocomplete**

Codeium offers a sophisticated autocomplete feature that significantly accelerates coding by predicting the next lines of code based on the current context. This feature supports over 70 programming languages, ensuring wide applicability across different development environments.

This feature is the core value add for Codeium, and it continues to work very well.

### *

Intelligent Code Search**

Codeium is teasing the addition of [AI-assisted code search](https://codeium.com/about_codeium_search) to their offerings. There’s currently a landing page to sign-up [here](https://codeium.com/waitlist/codeium-search).

I haven’t been able to try this out yet.

If this works anything like GitHub’s relatively recently overhauled code search feature, I’ll be very interested in trying it out.

**That said, I do already have shortcuts configured in my preferred IDE, Neovim, for finding symbols, navigating projects efficiently, going from method or function to the call sites referencing them, etc. ** I wouldn't consider this a must-have feature as a result, but I can see it increasing the overall value proposition of Codeium.

### *

AI

Driven Chat Functionality**

Another innovative feature of Codeium is its AI-powered chat, which acts like a coding assistant within the IDE. This tool can assist with code refactoring, bug fixes, and even generating documentation, making it a versatile helper for both seasoned developers and those new to a language or framework.

I tend not to lean on this feature very much when I’m coding - preferring to accept the code completion suggestions Codeium makes as I go.

That said, when I last evaluated Codeium’s and Copilot’s chat features they were quite similar.

Codeium’s AI-assisted chat functionality enables you to:

**Generate

** code in response to a natural language request

**Explain

** existing code that you may be unfamiliar with

**Refactor

** existing code in response to a natural language request

**Translate

** existing code from one language to another.

### *

Integration with Major IDEs**

Codeium prides itself on its broad compatibility with major Integrated Development Environments (IDEs), including Visual Studio Code, JetBrains, and even unique setups like Chrome extensions. This ensures that developers can leverage Codeium’s capabilities within their preferred tools without needing to switch contexts.

## *

Recent Updates and Enhancements**

As part of its commitment to continuous improvement, Codeium regularly updates its features and expands its language model capabilities.

Recent updates may include enhanced machine learning models for more accurate code suggestions and additional tools for code management and review processes.

Codeium documents all of its changes as Changeset blog posts on its official blog: https://codeium.com

### Series B

Codeium announced a $65M Series B round, making them one of the best funded companies in the Gen-AI for software development space.

### Partnerships

Codeium announced a set of strategic partnerships at the beginning of 2024:

Dell (on

premise infrastructure)

Atlassian (Source code management and knowledge stores)

MongoDB (Developer communities with domain

specific languages (DSLs))

### Termium - code completion in the terminal

A.K.A “Codeium in the terminal”. [Termium](https://codeium.com/blog/termium-codeium-in-terminal-launch) is the application of the same proprietary model to a new environment where developers could use some support: the command line prompt.

Used by developers to access remote machines, manage deployments of as many as one or thousands of servers, issues cloud environment commands, run database migrations and much more.

This prototype feature puts Codeium in direct competition with the [Warp AI-enhanced terminal](https://zackproser.com/blog/warp-ai-terminal-review) offering.

The Codeium team are being rightfully cautious about announcing and promoting this integration, because making mistakes in the terminal could be exceedingly costly for developers and the companies that employ them.

That said, because Codeium was intentionally built as a layer between the terminal and the end-user developer, intercepting and intelligently debugging stack traces before they even make it back to the developer does seem like a winning prospect.

### Codeium Live: free, forever up-to-date in-browser chat

Codeium released a prototype feature called [Codeium Live](https://codeium.com/blog/codeium-live), which allows developers to index and chat with external libraries from their browser.

This is a useful feature for any developer attempting to ramp up on a new dependency, which is something most working developers are doing regularly.

Codeium Live solves a common problem of hallucination in foundation models. Even though models such as OpenAI’s ChatGPT4 are amongst the best in terms of overall intelligence and accuracy of responses, they still suffer from out of date training data that doesn’t capture massive upgrades projects such as Next.js have undergone.

This means that asking for code samples or explanations from ChatGPT4 is likely to result in hallucinations - whereas Codeium Live is regularly updated with fresh source code for the dependencies and libraries it has indexed.

### Multi repository context awareness for self-hosted customers

This will allow developers to reference code in non-active private repositories as part of their Chat experience, as well as allow the Codeium context awareness engine to retrieve context automatically from other, potentially highly relevant, repositories at inference time.

## Usability and Developer Experience

### **User Testimonials, Reviews and Awards**

Codeium was [the only AI code assistant to be included in Forbe’s AI 50 list of the top private AI companies in the world. ](https://codeium.com/blog/codeium-forbes-ai-50)

Codeium was highlighted for:

Being the first AI assistant to GA [in

IDE chat](https://codeium.com/chat), [context awareness](https://codeium.com/blog/context

aware

everything

more

advanced

realtime

context

than

github

copilot), and more capabilities

Near 5

star ratings on all extension marketplaces

Broadest [IDE](https://codeium.com/download) and language availability out of any AI assistant

Unique personalization capabilities that tailor Codeium’s system to every company’s and individual’s specific codebases, semantics & syntax, and best practices (see [this](https://codeium.com/blog/context

aware

everything

more

advanced

realtime

context

than

github

copilot), [this](https://codeium.com/blog/finetuning

with

codeium

for

enterprises), [this](https://codeium.com/blog/codeium

context

pinning

launch), [this](https://codeium.com/blog/introducing

at

mentions) and [this](https://codeium.com/blog/remote

indexing

multirepo

announcement))

Working with the leading companies in every vertical, including regulated industries like finance, defense, and healthcare due to our self

hosted deployment and our better compliance guarantees (this and this).

Case studies with [Anduril](https://codeium.com/blog/anduril

case

study), [Dell](https://infohub.delltechnologies.com/p/making

the

case

for

software

development

with

ai/), [Clearwater Analytics](https://codeium.com/blog/clearwater

analytics

case

study), and [Vector Informatik](https://codeium.com/blog/vector

informatik

case

study) to highlight the broad appeal

[Dell](https://codeium.com/blog/dell

codeium

partnership

announcement), [Atlassian](https://codeium.com/blog/atlassian

codeium

announcement), [MongoDB](https://codeium.com/blog/mongodb

codeium

partnership

announcement), [CodeSandbox](https://codeium.com/blog/codesandbox

codeium

partnership

announcement), and more as strategic partners

Feedback from users highlights Codeium's impact on improving coding efficiency and workflow. Many report that its autocomplete and search features significantly reduce the time spent on coding and debugging, which is especially valuable in large projects.

The positive reception is evident in numerous reviews where developers express satisfaction with the seamless integration and time-saving capabilities of Codeium. [Codeium has been generally well-received on Product Hunt](https://www.producthunt.com/products/codeium), earning high praise for its extensive features and ease of use, particularly among users of JetBrains IDEs like Goland and PyCharm.

It's also noted for being effective across various IDEs, including VSCode, and for supporting a wide range of programming languages.

Users appreciate its ability to significantly enhance coding efficiency and the fact that it offers these powerful tools for free. It holds a rating of 4.75 out of 5 based on 28 reviews, indicating strong user satisfaction.

In comparison, GitHub Copilot also receives positive feedback but tends to be more appreciated within the GitHub community and those who heavily use Visual Studio Code. Copilot is praised for its context-aware code suggestions and seamless integration with GitHub's ecosystem, making it a favored choice for developers already embedded in that environment.

However, Copilot is a paid service, which contrasts with Codeium’s free offering, and this could be a deciding factor for individual developers or startups operating with limited budgets.

## Pricing and Accessibility

### **Free Access for Individual Developers**

Codeium is uniquely positioned in the market by offering its full suite of features for free to individual developers.

This approach not only democratizes access to advanced coding tools but also allows a broad spectrum of developers to enhance their coding capabilities without financial barriers.

By contrast, GitHub’s Copilot starts at $10 per month for individuals. GitHub may grant active open-source maintainers free access to GitHub Copilot on a discretionary basis.

### *

Enterprise Solutions**

For teams and businesses, Codeium offers tailored plans that provide additional features such as deployment in a virtual private cloud (VPC), advanced security options, and dedicated support. These plans are priced per seat, which allows businesses to scale their usage based on team size and needs.

## Security and Privacy

### *

Data Privacy**

One of Codeium’s strongest selling points is its commitment to privacy. Unlike some competitors, Codeium does not train its AI models on user data. This approach addresses growing concerns about data privacy within the development community.

### *

Security Features**

Codeium provides robust security features, including end-to-end encryption for data transmission and storage. For enterprise users, there are additional security measures such as role-based access controls and compliance with industry-standard security protocols.

### *

Transparency and Trust**

Codeium’s transparent approach to handling user data and its proactive communication about privacy practices have helped it build trust within the developer community. This is particularly important in an era where data breaches and privacy violations are common.

play

it

 Junior developers playing it real safe to avoid falling into traps

I've noticed some junior engineers are so worried about playing it safe, not making a mistake, not breaking anything, that they paradoxically end up hobbling themselves and stagnating their growth.

Stagnating and failing to grow to your fullest *actually will make you more likely to be fired*.

Symptoms of this condition include: avoiding raising your hand for things that seem scary like deployments, cutting releases, migrations, or feature development for tricky projects.

I think this is a real shame, because it's actually the safest and most opportune time to take the risks, make the mistakes, and learn the painful lessons that will stand by you and guide you for the rest of your career.

If this sounds like something you may be doing, if you currently believe you need to get N more evolutions or skills in place before you start going after what you really want to do, I invite you to rethink that.

Companies hiring you expect you to struggle and grow. They know that you are still trending toward your full potential. They may even have used that as justification to pay you less.

When the next seemingly scary migration, release, project, customer call, planning exercise, speaking event or presentation comes up...

HOP ON THE PAIN TRAIN

A frustated senior developer trying our your improperly tested dev tool for the first time

When I evaluate a new AI-assisted developer tool, such as [codeium](/blog/codeium-review), GitHub CoPilot or OpenAI's ChatGPT4, this is the thought process I use to determine if it's something I can't live without or if it's not worth paying for.

## Does it do what it says on the tin?

This appears simple and yet it's where most AI-assisted developer tools fall down immediately. Does your product successfully do what it says on the marketing site?

In the past year I've tried more than a few well-funded, VC-backed, highly-hyped coding tools that claim to be able to generate tests, perform advanced code analysis, or catch security issues that simply do not run successfully when loaded in Neovim or vscode.

## The two cardinal sins most AI dev tool startups are committing right now

1. Product developers working on the tools often test the "happy path" according to initial product requirements 1. Development teams and their product managers do not sit with external developers to do user acceptance testing

## Cardinal sin #1 - Testing the "happy path" only

When building new AI developer tooling, a product engineer might use one or more test repositories or sample codebases to ensure their tool can perform its intended functionality, whether it's generating tests or finding bugs.

This is fine for getting started, but a critical error I've noticed many companies make is that they never expand this set of test codebases to proactively attempt to flush out their bugs.

This could also be considered laziness and poor testing practices, as it pushes the onus of verifying your product works onto your busy early adopters, who have their own problems to solve.

## Cardinal sin #2 - Not sitting "over the shoulder" of their target developer audience

The other cardinal sin I keep seeing dev tool startups making is not doing user acceptance testing with external developers.

Sitting with an experienced developer who is not on your product team and watching them struggle to use your product successfully is often painful and very eye-opening, but failing to do so means you're pushing your initial bug reports off to chance.

Hoping that the engineers with the requisite skills to try your product are going to have the time and inclination to write you a detailed bug report after your supposed wonder-tool just failed for them on their first try is foolish and wasteful.

Most experienced developers would rather move on and give your competitors a shot, and continue evaluating alternatives until they find a tool that works.

Trust me - when I was in the market for an AI-assisted video editor, I spent 4 evenings in a row trying everything from incumbents like Vimeo to small-time startups before finding and settling on Kapwing AI, because it was the first tool that actually worked and supported my desired workflow.

hearing

aids

hearing

aids

hearing

aid

safety

hearing

aid

life

hearing

aids

---

## Tell me where to send my money!

In this post I will dream big about "super hearing aids" that extend my senses and enhance my intelligence.

I don't believe we're that far off from this product existing, so if you know of something similar, feel free to leave a comment at the bottom of this page.

### LLM onboard with vector storage and internet access

They need to be small enough to be comfortably worn, yet I expect unreasonably long battery life. They should be hard to notice yet unobstrusively enhance their wearer's senses and intelligence.

For example, an onboard LLM could auto translates any incoming language to my native tongue, help me remember important facts, dates, alert me that I'm going to be late...

They can be subtly signaled to start or stop listening and recording things and then eventually rules can be set based on context (never record inside my home).

### Pushes me real time alerts that relate to my safety or my family's safety

In essence, the super hearing aids are a blend of our best state of the art technologies that are easy to keep on me all day and which attempts to help keep me alive.

They will remind me how to retrace my steps and guide me back to where I parked my car, but also alert me to dangers I might not be aware of on my own. Even giving me advance warning on a danger I would otherwise detect myself but later on would be advantageous.

This includes alerting me of events nearby such as shootings, extreme weather, traffic congestion, police reports that are relevant to my immediate safety, etc.

They could automatically call and route family and EMTs to my location if I fell ill or lost consciousness...

### Seamlessly connects and disconnects from entertainment systems

When my super hearing aids aren't actively preserving my life, I'll expect them to preserve the ideal audio balance so that I can hear what's important.

I'd like them to pair automatically with my devices so I can hear the TV across the room without blasting everyone else in the room with higher volume.

I'd like to be able to hear my game and have a conversation at the same time.

For less leisurely times, such as in stressful interpersonal conflicts, or certain professional settings, I'd like to be able to switch on recording and auto-transcription, ideally without it being obvious.

My expectations are high. What's going to be the first device to check off all these boxes?

vector

I'm pivoting from a career of pure software engineering roles to developer advocacy!

I'm excited to share that I'm joining Pinecone.io as a Staff Developer Advocate!

## Why Pinecone?

Pinecone.io makes the world's most performant cloud-native vector database, which is essential for storing and efficiently querying embeddings. Vector databases are **critical infrastructure** in the midst of the AI revolution we're currently experiencing.

Over the last half year or so, I've been getting deeper into Generative AI and its many use cases, particularly focusing on how to improve and enhance developer workflows (such as my own) with tools like ChatGPT, [Codeium](/blog/codeium-review), etc.

But the main thing that got me interested in Pinecone was their outstanding technical content, and the very talented team behind it. My first introduction was [James Briggs's outstanding YouTube videos](https://www.youtube.com/c/JamesBriggs), which led me to [Pinecone's own learning center](https://pinecone.io/learn).

As I got deeper into their examples, ran some of them and experimented, I was blown away at the quality and amount of deep technical content that Pinecone was giving away for free. If you've read my work before, you know I'm fanatical about open sourcing things and sharing learning for free with other developers.

I've also written about [my initial, very positive, experiences with Vercel](/blog/maintaining-this-site-no-longer-fucking-sucks), and, in addition to James's technical content, I learned that another very talented staff developer advocate, [Roie Schwaber-Cohen](https://www.linkedin.com/in/roiecohen/), was shipping [outstanding Vercel templates that implemented full-stack GenAI chatbots](https://vercel.com/templates/next.js/pinecone-vercel-ai) complete with Retrieval Augmented Generation (RAG).

If you want a great intro to what vector databases are and how embeddings can give AI models accurate long-term memories (and prevent hallucinations), check out this great intro by my colleague Roie.

After considering a few other opportunities, I found that I was most excited about joining this team and going deep on Generative AI use cases and vector databases.

## Why pivot to Developer Advocacy?

In short, I've figured out that I love technical storytelling as much as I love building systems and applications.

I've always been a writer, but since January of 2023, I've found myself increasingly itching to not only produce technical tutorials and deep-dives, but also share them with my fellow developers.

I also enhanced my production pipeline, modernizing my portfolio site and migrating to Vercel for more rapid iteration, as  I wrote about here.

My game plan was simple: continue to do my day job as an open source developer, continue to learn in public, but also capture my best learnings, insights, favorite new tech stacks and tools via my blog, YouTube channel and even Twitch!

Learning in public has brought me a lot of personal joy, especially when I share my learnings with others and get feedback that it helped even one other developer. When I found the opening at Pinecone for a developer advocate that would combine:

development of applications to demonstrate the latest and most effective patterns around applications that leverage the latest AI technologies

open source development to assist the overall community and make it easier to build AI applications

technical storytelling

I knew the time had come to jump in, join the team, and go deep on a completely new technology stack, after spending the last 3 and a half years immersed in Infrastructure as Code, SDK and tooling development and deployment automation.

## Why does this mean so much to me?

I am a self-taught developer. When I was starting out, and to this day, the work of others who have been generous enough to decide to write up their insights, code blocks, special hacks, secret fixes to that one weird error message, etc and share them with the world, have helped me immensely in my quest. I do the same to give back to the developer community and broader technically-minded community.

## Pinecone is hiring!

If you're interested in joining the Pinecone team, check out this page!

3

an

lua

 For the last couple of years, I've been happily using the community open-source project AstroNvim, which extends Neovim into a full-fledged IDE experience

## Table of contents

## What is AstroNvim?

AstroNvim is [an open-source project](https://github.com/AstroNvim/AstroNvim) that tastefully composes many open-source Lua plugins for Neovim together to create a complete Integrated Development Environment (IDE) experience in your terminal.

Think of it like vim extended with filetree, LSP support, git integrations, language symbol lookups and more.

Here's what AstroNvim looks like when I'm running it in a tmux session:

In other words, think of VSCode or one of JetBrains' IDE offerings, but completely free, with no sign-up required and you're also on your own when things go bad.

That last part is not entirely true, of course, because you've got the whole helpful community in GitHub and elsewhere who share configurations, bug reports, patches and workarounds with one another online.

 If you do not configure AstroNvim at all, it comes out of the box with batteries included - everything from debugger to support for any programming language or syntax

## What does AstroNvim configure out of the box?

Common plugin specifications with [AstroCommunity](https://github.com/AstroNvim/astrocommunity)

Statusline, Winbar, and Tabline with [Heirline](https://github.com/rebelot/heirline.nvim)

Plugin management with [lazy.nvim](https://github.com/folke/lazy.nvim)

Package management with [mason.nvim](https://github.com/williamboman/mason.nvim)

File explorer with [Neo

tree](https://github.com/nvim

neo

tree/neo

tree.nvim)

Autocompletion with [Cmp](https://github.com/hrsh7th/nvim

cmp)

Git integration with [Gitsigns](https://github.com/lewis6991/gitsigns.nvim)

Terminal with [Toggleterm](https://github.com/akinsho/toggleterm.nvim)

Fuzzy finding with [Telescope](https://github.com/nvim

telescope/telescope.nvim)

Syntax highlighting with [Treesitter](https://github.com/nvim

treesitter/nvim

treesitter)

Formatting and linting with [none

ls](https://github.com/nvimtools/none

ls.nvim)

Language Server Protocol with [Native LSP](https://github.com/neovim/nvim

lspconfig)

## Why would I do this to myself?

 SERIOUSLY IT'S LIKE A FULL IDE RIGHT IN YOUR TERMINAL AND ALL YOU HAVE TO DO IS...

Around now you may be starting to say to yourself, "Self, I think this guy is losing it, and/or something of sadist". And you'd be right. But there's actually more than one reason I willfully continue to do this to myself:

### AstroNvim is free

I pay for enough software subscriptions as it is. I still have VSCode, Cursor, and even some Jetbrains IDEs installed and lying around, I generally do not find myself reaching for them unless they're the only places I can run a particular extension I need.

### AstroNvim is fast

I am one of those people who spends the few seconds it takes VSCode to fire all your plugins' phone-home-and-notify subsystems screaming in his head.

Being greeted by the stuttering multi-layered wall of opaque notifications that do not idenitfy which extension they're talking about every time I have the audacity to open a file for editing is just the wrong kind of abuse in my book.

I like that, despite AstroNvim including plenty of the functionality I need to get my job done effectively, it remains snappy on boot and when loading large files.

### AstroNvim plays well with Tmux

It's even snappy when I have multiple instances of Neovim open in different tmux panes. I've arrived at my tmux-based workflow after many years of careful considerations, cycling through new tools and approaches and giving all of the major IDEs, paid and free, their own period of careful contemplation and editing time both at work and on personal projects.

Tmux, and the ability it grants me to effortlessly resize my workflow to the scope of my current task, are for me.

### AstroNvim is highly extensible and Neovim's community is prolific

It takes some time to become comfortable with Lua syntax if you've never seen it before. It takes some head-scratching, quitting, killing Neovim, re-opening it and trying hotkeys again before your first few plugins install smoothly.

 This is all the code required to flip your colorscheme to `Catpuccin`

But after a little bit it's pretty comfortable and fun to find new Neovim plugins and install, combine and configure them to your liking.

Everything from AI-enhanced tool integrations to spellcheckers, link utilities that open the source code under your cursor in GitHub for you and copy the URL to your clipboard and much more are available as open-source Neovim plugins on GitHub.

For example, right after OpenAI initially set the world on fire with ChatGPT, [this handy Neovim plugin emerged on GitHub: NeoAI](https://github.com/bryley/neoai). As you can see, it integrates directly in Neovim, so you can copy paste the example config from the project README and modify it to your heart's content.

There's a ton of tools now that allow you to interact with OpenAI and a wide range of local models, but my point is that this is only one of many amazing free community-provided Neovim plugins that popped up essentially immediately to address fellow Neovim users' needs.

## What are my favorite customizations?

Luckily, because AstroNvim itself is open-source, and the [project docs recommend using the provided config template repository to store your personal customizations](https://docs.astronvim.com/configuration/manage_user_config/), you don't have to ask me to see [my config](https://github.com/zackproser/astronvim_config).

Hell, you can even jump directly to [the commit](https://github.com/zackproser/astronvim_config/commit/ec850dd06fae803219f786c3ab1099bb8645c731) where I installed my favorite Gruvbox dark theme.

databases

## Table of contents

## vector database comparison: Pinecone vs Chroma

This page contains a detailed comparison of the Pinecone and Chroma vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Pinecone | Chroma | | ---------| -------------| -------------| | Local Deployment | ❌ | ✅ | | Cloud Deployment | ✅ | ✅ | | On - Premises Deployment | ❌ | ✅ |

## Scalability | Feature | Pinecone | Chroma | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ✅ | | Vertical Scaling | ❌ | ✅ | | Distributed Architecture | ✅ | ✅ |

## Data Management | Feature | Pinecone | Chroma | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ❌ | ✅ |

## Security | Feature | Pinecone | Chroma | | ---------| -------------| -------------| | Authentication | ✅ | ✅ | | Data Encryption | ✅ | ✅ | | Access Control | ✅ | ✅ |

## Vector Similarity Search | Feature | Pinecone | Chroma | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean | Cosine, Euclidean | | ANN Algorithms | Custom | Custom | | Filtering | ✅ | ✅ | | Post-Processing | ❌ | ✅ |

## Integration and API | Feature | Pinecone | Chroma | |---------|-------------|-------------| | Language SDKs | Python | Python | | REST API | ✅ | ✅ | | GraphQL API | ❌ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Pinecone | Chroma | |---------|-------------|-------------| | Open-Source | ❌ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Pinecone | Chroma | |---------|-------------|-------------| | Free Tier | ✅ | ❌ | | Pay-as-you-go | ✅ | ✅ | | Enterprise Plans | ✅ | ✅ |

--

Read article

background

## Table of contents

Have you ever wanted to return a response immediately from a Vercel API route while still performing long-running tasks in the background? Would you rather not pay for a queue or add another component to your architecture?

In this post I'll demonstrate how you can keep your job-accepting API route snappy while still performing long-running or resource intensive processing in the background - without impacting your end-users. ## The problems

I recently needed to solve the following problems with my Next.js application deployed on Vercel: 1. **I needed my job-accepting API route to return a response quickly**. 1. **I had expensive and slow work to perform in the background for each new job**. 1. **Time is not unlimited. ** Vercel capped function invocation timeouts at 5 minutes for Pro plan users. I didn't want to risk doing so much work in one API route that it was likely I'd hit the timeout. I wanted to divvy up the work.

## The solution

The solution is to use a fire and forget function. Forget, in the sense that we're not awaiting the response from the long-running process.

Here's what my fire-and-forget function looks like in my app [Panthalia](https://github.com/zackproser/panthalia):

This is a React Server Component (RSC) by default because it does not include the `'use client'` directive at the top of the file, and because Next.js routes default to being server components.

Because this is a server component, we actually have to determine the `${baseUrl}` for our API call - whereas you may be familiar with calling fetch on a partial API route like so:

`await fetch('/api/jobs', {})`

in our server component we must supply a fully qualified URL to fetch.

### Calling functions that call other API routes

The `startBackgroundJobs` function is really just an API call to a separate route, `/api/jobs` which POSTs to that route the information about the new post including its ID.

Everything that other route needs to start processing work in a separate invocation. Meanwhile, the `startBackgroundJobs` call itself is quick because it's making a request and returning.

This means the API route can immediately return a response to the client after accepting the new task for processing:

## Wrapping up

And there you have it. Using this pattern, you can return a response to your client immediately to keep your application quick and responsive, while simultaneously handling longer-running jobs in a separate execution context.

---

On December 6th, 2023, Pinecone and Cohere held a joint meetup at the Andreesen Horowitz offices in downtown San Francisco.

I was one of the speakers, along with Jay Alammar from Cohere and Kat Morgan from Pulumi.

I gave a talk titled, "Navigating from Jupyter Notebooks to production" that announced the [Pinecone AWS Reference Architecture with Pulumi](/blog/pinecone-reference-architecture-launch).

I used a an extended mountaineering metaphor to compare getting the kernel of your application working in a Jupyter Notebook to arriving at base camp.

In some ways, you're so close to production and yet still so far away, because you need to come up with good solutions for secrets, logging, monitoring, autoscaling, networking, and on and on.

I then announced the Pinecone AWS Reference Architecture using Pulumi and talked through what it was and how it can help folks get into production with high-scale use cases for Pinecone faster.

Using Pulumi, I was able to build the initial version of the Pinecone AWS Reference Architecture in about a month, start to finish.

I gave a brief introduction to Infrastructure as Code (IaC) and explained why it's a very powerful tool for working with cloud providers such as AWS or GCP.

I also candidly explained the pitfalls of introducing IaC to a team that doesn't have much cloud development experience, and explained how easy it is for cognitive load and overall complexity to skyrocket past the point of the offering being useful.

I related my experiences around mitigating the pitfall of added complexity by intentionally cross-training your entire team in working with IaC and deploying and destroying any architectures you may produce together.

The default is for this knowledge to become siloed across one or two developers who originally worked on the system, but the key is to ensure everyone on the team is equally confident in working with IaC and cloud providers, via cross-training and practice.

The event was great and we had nearly 125 folks show up to network, eat and listen to our talks. A huge thank you to the folks at Andreesen and Horowitz for their hospitality, support and excellent venue.

---

This was the fourth article I published while working at Pinecone:

Read article

|  When  |   I feel | |---|---| | My program compiles after an onerous redactoring|  elation| | People add meetings to my calendar to talk through deliverables they haven't thought through or locked down yet| like my precious focus time is being wasted   | | Another developer says something I created was helpful to them| like a link in a long chain stretching from the past into the future| | Someone downloads my code, tool or package| absolutely victorious | | I ship something important | A mixture of satisfaction, hope and dread that I've missed something critical| | My pull request is merged | absolutely victorious |

---

## Evaluating Retrieval Augmented Generation pipeline performance

I wrote the RAG evaluation chapter for Pinecone's latest learn series: Vector Databases in Production for Busy Engineers.

Read chapter

with

the

my

light

was

There are only two people I am aware of named Zachary Proser, with that exact spelling. This article is only for Zachary Proser, but it is published live on the internet for other reasons.

Zachary, when you are older, it is my hope that you will find this writing in some form and be able to better understand me through it. I left it solely for you and solely for that purpose.

I was born with fiery drive at the core of my being. I have always felt it, and pictured it as a small orb of white and gold fire that I had to hide and keep safe from everyone around. That is what drove me through the experiences I'm about to share with you. I was able to see inside myself what I could make using this light, but nobody else could see what would be lost if the light went out. That is the only reason I am here and able to write this to you.

There is the abusive tier of fathers, and the shameful tier of fathers, the absent and the stealing tiers of fathers, and the tier of fathers who make their families feel unsafe. And then there's the garbage-band of fathers below these. Your paternal grandfather was below all of these, in a liquid dogshit tier all to himself, which combined and expanded upon all of the tiers above.

Your paternal grandmother was at her core a sensitive and kind woman, but completely broken by trauma and other transgressions against her before she ever met your grandfather. What kindness and nurturing behaviors I have came from her, but unfortunately being with him only further injured, insulted and ultimately broke her, too. She told me when I was eleven that she had wanted to get away from him but was afraid.

This piece is important: the horrors of the human world stem mostly from an epidemic of unhealed trauma. Hurt people hurt people. This trauma is an inheritance we mostly receive from our parents.

Your grandfather was a deeply injured, totally broken, angry, drunk, distant, depressed and highly anxious person. In fairness to him, his father was an even bigger piece of shit from all accounts; a mobster and frontman for the mafia who helped them open and run nightclubs. Your grandfather told me stories of not seeing his father for months at a time, then being surprised to see him come home, usually drunk and temporarily flush with cash, carrying groceries and vegetables from the farmer's market for him and his four brothers.

Inevitably, within another couple of weeks, he'd have disappeared again and your paternal great grandmother was totally out to lunch on pills and alcohol, so my father said he would be embarassed yet hungry enough to go knock on his neighbors' door and ask if he could eat some chef boyardee with them.

I remember the regular drives my father and mother would take me on to Pennsylvania where we'd visit his mother in the asylum. It always smelled like diarrhea, distinfectant and the rotting flesh of dying people, and I always came into the room with him and saw how further broken he was to see his vegetable mother babbling in the hospital bed, calling him by a name that none of his five brothers ever had. When my father was at home and not in a warzone for months on end chasing a story or working the exact late shift that made it impossible to be present with my mother, myself and my brother, he'd sometimes get calls from that asylum because the nurses were legally obligated to tell him that his mother had fallen down. I overheard about a hundred of these calls over the years.

The most important memory I have with my father was when I was just a couple years older than you are now, and we were out in the middle of nowhere in the farmhouse he had bought to try to reproduce his family home, and he and I were sitting in the living room and I was playing with blocks while he sat on the edge of the table because my mother had told him to spend time with me. He was still skinny because he was in his late 20's, and he looked absolutely miserable.

I was playing with the blocks the way you do now, and in my excitement one flew out of my right hand and caught him on the edge of his forehead. He didn't react much, except to sigh deeply and allow his face to sink even deeper into grief as he stared at me, and even at that age I could read his innermost thought as "How in the fuck did I end up with a fucking kid?". I knew at that moment he didn't like me, and we never got past that event, whether he realized it or not.

I spent the majority of my childhood in a state of fear or anxiety. When I would play little league baseball at seven years old, I had stomach pains so intense that I would drink an entire bottle of adult-strength Mylanta to get through a normal practice. The coach and other adults found it humorous and repeatedly chided me for turning the team into "The Mylanta Braves".

There is one thing my father said to me that appears to have been true:

"It's all about the compound interest." He meant this in the sense that if you save a small bit of money early in your life and forever afterward continue to sock away more in an account that earns interest, you'll eventually have a lot of money. But his words were true in the perverse sense for my entire late childhood and adult life - wherein the fraudulent debts that he forged in my name continued to earn interest making them inescapable even when making the monthly $650 payments on one of them.

When I was 17, I started getting letters in the mail from corporations I didn't recognize, asking for payments on loans I knew nothing about. At this time I was working in my friend Tom's landscaping business for $10 an hour and occasionally doing moving jobs for up to $15 per hour.

My father had written at least $80,000 of bad debt in my name, by forging my signature despite the fact that I was minor at the time, and following in his own father's footsteps as a fraudster. For 17 years, the bills kept coming in and I kept paying them as my father and mother insisted, and, for years, my mother insisted she had no knowledge of the bad debt or where it had come from. To this day, despite all the therapy and self-work, I am unable to receive physical mail without some panic, and I bristle when someone asks me for my phone number or, far worse, gets it from somewhere other than asking me and uses it to contact me.

In my mid 20's I made the profound error of marrying the wrong woman, who was a tormented addict like me and therefore felt like home. We never should have been together. If I had had a healthy sense of self-esteem at that time I wouldn't have seen her a second time. Instead, I ended up marrying her, losing six years of my life to her abuse and the abuse of her ignorant, broken, alcoholic family. When we were going through a divorce, her parents had nothing better to do than hold hostage every painting, drawing and half-finished canvas I had ever produced in my life, out of pure spite, because I'd made the mistake of storing all my artwork with them when we drove across the country and visited them.

Eventually, in my early 30s, when I had become an unwilling old-hand at extracting information from corporations, suing them, and being sued in return by them, I was able to retrieve the original paperwork for the many loans my parents forged in my name, and saw that, in addition to the loans that my father had forged my signature on, there were some loans my mother had also forged my signature on. What hurt me the most is that I had been making artwork and signing it with my distinct signature and giving them some of it for several decades by that point, but neither of them even attempted to make their handwriting look like my signature. Perhaps they didn't know what it looked like.

While we shouldn't ever have gotten married and would have ended up in the same way regardless, I'll say that we spent years of youthful evenings and weekends fighting bad debt and fraud that both of our families had doomed us to. If you truly wish harm on someone in this world, you should not break their bodies but instead simply steal their identity.

It is a horror that perpetually births additional horrors. I know that the worst part of not having enough money is the way people who don't know you talk to you and how corporations treat you. From teenage years, through my thirties and sometimes still to this day, I will "wake up" from a flashback of pure rage and realize that I'm standing on a street corner having forgotten to cross, or that I'm walking down the aisle of a store in such a fury that I want to beat a stranger until their head comes apart in my hands.

My mother also had a rage issue, which I now understand given what I know she had lived through, and once almost blinded me by driving me home from where she lost her temper in a state of pure fury, doing double and triple the speed limit, running red lights and then, when we retured home and I fled upstairs to my room, hurling her giant mass of keys and other metal objects the size of a softball at the wall right next to my face. If she had found her target I wouldn't have been able to write all the software that paid for the alimony and most of the bad debt.

I could have forgiven my mother. Despite her issues, she had shown me the only tenderness I had known in my childhood and we had long periods of being very close, left alone together in the large house in the middle of nowhere for months on end. However, I was so enraged at my father and so desperate to get my pound of flesh and to injure him in return in this life, that I buried her in the ice along with him, and I told them that I didn't want to hear from either of them ever again. I knew in my heart that she would do my work for me. In her grief at losing me as a son, she would remain the thorn in his flesh for the remainder of his life, and my desire to harm him exceeded what love I had left for her at that point. Zachary, this is what rage is capable of doing. Never underestimate the force of rage in a human being.

In the final analysis, the story of my parents and I became the story of learning to hide my fiery orb of creative drive, flying under the radar, learning to keep my actions, movements and intentions concealed, until I was able to break away, survive them and ultimately abandon them completely. Most people I've tried explaining this piece to do not understand, but for me this was a truly a matter of deciding whether or not I would be destroyed or survive them, and removing them completely was the only way I was able to ensure that I would come through with the ability to still create and eventually heal. Nobody else understood what was at stake if the orb went out, but I could see inside myself what I could make using it - what works could come to be, if I lived, that otherwise would remain unrealized.

Like your grandfather, I was born with atrial fibrillation. When I was three years old, I complained of "heart races" but my parents mostly thought I was just anxious. I was given Holter monitors to wear for 24 hours, which didn't pick up the "heart races". I was an athlete for most of my life until my mid 30s when careerism and high functioning alcoholism replaced my workouts.

I used to walk myself to the emergency room in Berkeley, California at 3AM because I was always awoken at that hour by my heart beating so erratically, sometimes slamming in my chest near 180 beats per minute, then going 3 seconds or longer in stillness. By my count I did this at least 13 times in my early 30's and I was still unable to receive a diagnosis. I was told by the doctor on-call one evening that there was nothing wrong with my heart and that I needed to get help with my alcohol problem. He glared at me with contempt and disgust.

My primary care physician that Kaiser assigned me also was unable to diagnose me successfully, and spent years telling me that I had a nervous disorder that made it feel like my heart was beating out of rhythm.

Eventually, when my workaholic tendencies met with a very intense tech job, long hours, high functioning alcoholism, and an utterly savage four year divorce process where my ex would show up to work and regularly harass me, and show up to our court hearings at 9AM too drunk to respond to the judge verbally, the scales were finally tipped on my body. I was diagnosed in the same week with Grave's disease and atrial fibrillation. Perhaps mercifully, it was becoming completely hyperthyroid that finally exposed my arrythmia long enough for the emergency room to catch it on an EKG and tell me what I had known my entire life: I had progressive heart disease that was quickly worsening. I was 34 years old.

One of my worst fears up until that time was having to go through the same heart surgery, Radio frequency or RF ablation, that I had watched my father undergo and fail to obtain any relief from. He was also a totally recalcitrant patient, and refused to stop being sedentary, drinking daily to the point of blackout, smoking cigarettes, eating red meat and cheese and remaining steeped in his anger, rage and unaddressed wounds. For some reason, my mother and father paid a cardiologist to counsel him unsuccessfully for decades but she was ultimately unable to reach him. Due to his poor example, I was able to eventually pull myself away from all of the habits he and I shared and do things in the opposite way. I had my heart arrythmia successfully repaired by undergoing the same surgery when I was 35 years old. As I write this, I've experienced two and a half years of my heart beating mostly in rhythm, something many people take for granted, and my autoimmune disease is in remission.

For myself, I found sobriety on my eighth attempt to stop drinking, with a great deal of therapy, reading, and self-reflection. I was able to stop for good after I met and was with your mother and when I decided that it was more important to me to reach my full potential creatively and professionally and emotionally than it was to continue to be able to drink alcohol. This is a large part of the story around why I've been so intolerant about adults with unchecked substance abuse issues being able to visit with you when you were growing up.

There are two times in my life that I came close to bowing out, having felt that I had reached the limit of my pain tolerance. The first was following a bad drunken fight with the woman I mistakenly married first. I found myself in the woods off the sidewalk of downtown District of Columbia, leaning against a tree and looking for a sharp enough rock to open my veins.

The second time was during the worst of the divorce that buttressed perfectly my finally getting out of debt after 17 years of struggling with it. I stood in the bathroom of my bachelor's pad in Berkeley, California, and held my loaded Glock all the way into the back of my mouth with my index finger wrapped around the trigger. It was dark and I stood in front of the mirror, thinking about it silently for a very long time. Even then, feeling the most broken in my body and mind, I had some inclination of the fiery orb of light that I had been entrusted with, and the feeling that it perhaps encompassed something even larger and more important than just my creative abilities. Perhaps it was just the heat of being hyperthyroid for who knows how many years?

But I couldn't allow myself to put everything down at that point, for reasons I don't truly understand myself. I felt compelled to see through whatever my internal drive to keep going was trying to tell me.

Several times, people who came to understand a significant portion of my life, such as co-workers who came to know me well, asked how it was I was able to keep going through the multi-layered hardships I had while still pushing myself so hard to learn and improve. Often it was physicians or medical personnel, who were able to see my medical records and understand the number and severity of the injuries I had sustained and the stack of chronic illnesses that I had learned to live with, who expressed the most incredulity. I didn't have a way then to talk about the fiery orb of light and I wasn't sure that it was okay to share anything about it.

And of course, there were life's own vicissitudes. I've had pneumonia five times, once with a fever of 104.7 which caused me to hallucinate. I contracted the parasite Giardia when living in India after one careless, exhausted early morning flick of my toothbrush through a local tap instead of bottled water. They gave me pills with "a lot of heavy metals in them" which also caused me to hallucinate. I have walked away from five car accidents so far, one of which was a hit and run and the latest of which was fatal, when an older gentleman in a mustang failed to stop at a red light on the two lane highway we were driving home on from our beach vacation. His car went inside of the SUV in front of him, inches away from your mother, who was still pregnant with you. People have tried, and failed, to mug me while I was taking public transit in San Francisco; more than once. I've spent long hours unconscious on the street abroad in various countries - and somehow always woke up.

And now, at the end of my 37th year on this planet, I am in better physical and mental shape than I have ever been before. I will never drink alcohol again. What helped me the most was regular therapy with an excellent therapist, a lot of reading about PTSD, trauma, healing and emotional intelligence, occasionally medication where needed (specifically for insomnia - there's no other way for me to fall or remain asleep), and lots of support from good friends and the love of your mother. Above all else, I am now focused on protecting and raising you and teaching you everything that I know. I have only two goals for your youth, after which everything you do or become will be up to you:

1. That you will never understand what it means to have an absent or abusive father 2. That you will not develop PTSD in your childhood

More than anything, I wrote this for you because it is important to me that you understand what you mean to me, personally.

Zachary, when I am with you, holding you, or playing the games we already have together, or walking with you through the woods and teaching you words, I can feel my brain re-wiring itself.

I can feel, for the first time in 37 long years, the hint of a salve being applied to a wound that I could not get at with decades of therapy, or intermittent meditaton, or traveling the world, or blackout drinking, or drugs, or any other form of addictive self-soothing behavior. And you can be certain that I tried them all.

You and I, together, Zachary, are finally undoing a cosmic ill, addressing a core grief permeating all of my cells, that has bound both sides of our family tree in a cycle of suffering for countless generations.

Ask your maternal Grandfather how I looked at him the last time he was drunk around you in our home and he will tell you that I am willing to kill for you.

Ask your mother how many times I have raged about some perceived slight against the sanctity of our family home or a possible threat to your well-being, and she will tell you that I am willing to die for you.

But for you, Zachary, and for you alone, I am willing to do something even harder: remain. At long last I have come to understand that fiery orb of light was not just my own drive to survive and create. It was you.

databases

## Table of contents

## vector database comparison: Pinecone vs Milvus

This page contains a detailed comparison of the Pinecone and Milvus vector databases.

You can also check out my [detailed breakdown of the most popular vector databases here](/blog/vector-databases-compared).

## Deployment Options | Feature | Pinecone | Milvus | | ---------| -------------| -------------| | Local Deployment | ❌ | ✅ | | Cloud Deployment | ✅ | ❌ | | On - Premises Deployment | ❌ | ✅ |

## Scalability | Feature | Pinecone | Milvus | | ---------| -------------| -------------| | Horizontal Scaling | ✅ | ✅ | | Vertical Scaling | ❌ | ❌ | | Distributed Architecture | ✅ | ✅ |

## Data Management | Feature | Pinecone | Milvus | | ---------| -------------| -------------| | Data Import | ✅ | ✅ | | Data Update / Deletion | ✅ | ✅ | | Data Backup / Restore | ❌ | ✅ |

## Security | Feature | Pinecone | Milvus | | ---------| -------------| -------------| | Authentication | ✅ | ✅ | | Data Encryption | ✅ | ❌ | | Access Control | ✅ | ✅ |

## Vector Similarity Search | Feature | Pinecone | Milvus | |---------|-------------|-------------| | Distance Metrics | Cosine, Euclidean | Euclidean, Cosine, Jaccard | | ANN Algorithms | Custom | IVF, HNSW, Flat | | Filtering | ✅ | ✅ | | Post-Processing | ❌ | ✅ |

## Integration and API | Feature | Pinecone | Milvus | |---------|-------------|-------------| | Language SDKs | Python | Python, Java, Go | | REST API | ✅ | ✅ | | GraphQL API | ❌ | ❌ | | GRPC API | ❌ | ❌ |

## Community and Ecosystem | Feature | Pinecone | Milvus | |---------|-------------|-------------| | Open-Source | ❌ | ✅ | | Community Support | ✅ | ✅ | | Integration with Frameworks | ✅ | ✅ |

## Pricing | Feature | Pinecone | Milvus | |---------|-------------|-------------| | Free Tier | ✅ | ✅ | | Pay-as-you-go | ✅ | ❌ | | Enterprise Plans | ✅ | ✅ |

core

 Warp brings AI assistance into your terminal to make you more efficient

## Table of contents

Warp is an AI-assisted terminal that speeds you up and helps you get unblocked with LLM-suggested command completion, custom workflows and rich theme support.

Unlike command line tools like [Mods](https://github.com/charmbracelet/mods) which can be mixed and matched in most environments, Warp is a full on replacement for your current terminal emulator.

## The good

### It works

The core experience works out of the box as advertised: it's pretty intuitive to get help with complex commmands, ask about errors and get back useful responses that help you to move forward more quickly.

### It's pretty

It's also great to see first-class theming support and I will say that warp looks great out of the box - even using the default theme.

## The painful

### No tmux compatibility currently

I'm an avowed tmux user. I love being able to quickly section off a new piece of screen real estate and have it be a full fledged terminal in which I can interact with Docker images or SSH to a remote server or read a man page or write a script.

I like the way tmux allows me to flow my workspace to the size of my current task - when I'm focused on writing code or text I can zoom in and allow that task to take up my whole screen.

When I need to do side by side comparisons or plumb data between files and projects, I can open up as many panes as I need to get the job done.

Unfortunately, at the time of writing, [Warp does not support Tmux](https://github.com/warpdotdev/Warp/discussions/501) and it's not clear how far away that support will be.

### Sort of awkward to run on Linux

I have another quibble about the default experience of running warp on Linux currently:

It's currently a bit awkward, because I launch the `warp-terminal` binary from my current terminal emulator, meaning that I get a somewhat janky experience and an extra floating window to manage.

Sure, I could work around this - but the tmux issue prevents me from making the jump to warp as my daily driver.

### You need to log in to your terminal

I know this will bother a lot of other folks even more than it bugs me, but one of the things I love about my current workflow is that hitting my control+enter hotkey gives me a fresh terminal in under a second - I can hit that key and just start typing.

Warp's onboarding worked fine - there were no major issues or dark patterns - but it does give me pause to need to log into my terminal and it makes me wonder how gracefully warp degrades when it cannot phone home.

Getting locked out of your terminal due to a remote issue would be a bridge too far for many developers.

## Looking forward

I'm impressed by warp's core UX and definitely see the value.

While I do pride myself on constantly learning more about the command line, terminal emulators and how to best leverage them for productivity, it's sort of a no-brainer to marry the current wave of LLMs and fine-tuned models with a common developer pain point: not knowing how to fix something in their terminal.

Not every developer wants to be a terminal nerd - but they do want to get stuff done more efficiently and with less suffering than before.

I can see warp being a great tool to helping folks accomplish that.

Check out my [detailed comparison of the top AI-assisted developer tools](/blog/ai-assisted-dev-tools-compared).

the

system

in

your

One of the best engineering managers I've ever worked with was at Cloudflare.

Let's call him Tom, because [his real name is Thomas Hill](https://www.linkedin.com/in/thomas-hill-6ab4b06/).

What did Tom do in his free time when he first arrived at Cloudflare? Read version control.

Especially the joints where his team's code integrated with that of other teams.

What was the result of his lucubration?

Tom knew everyone's systems better than they did and could facilitate and cut through the bullshit in meetings due to his deeper technical knowledge.

When he found himself in a contentious debate, or in a situation where another EM was trying to foist something onto his team that didn't really belong to us, Tom had the upper hand because he'd already done all the research.

I remember being somewhat in awe of this ability of Tom's. Someone from the security team might wander into our sector wanting to know more about how API keys were generated and managed.

Tom recited it from memory even though he was not expected to be shipping code himself.

There are a couple of caveats to this approach:

1. You need to work at a large enough organization to have multiple teams but central version control 2. You need to care enough to be good at what you do. This won't work if you're trying to phone it in

The longer I do this job, the more I notice the most effective developers and technical leaders doing the same thing. They have an impressive map of their company's territory in their heads.

They can point you to where you need to go - even if they haven't committed to the repositories they're referring to.

Trace the whole system in your head. Even the parts that don't belong to your team!

databases

in

production

for

busy

---

## Integrated cloud-based vector databases in your CI/CD pipelines

I wrote the CI/CD chapter for Pinecone's latest learn series: Vector Databases in Production for Busy Engineers.

Read chapter

## Codeium is a GitHub Copilot alternative that I'm trying out

Read my updated Codeium analysis

The world of A.I. enhanced developer tooling moves very quickly, but you still may not yet have heard of Codeium, an open source and free alternative to GitHub's Copilot.

## What's wrong with GitHub Copilot? I'll focus on the perceived quality of Copilot's code completion capabilities.

At the time of this writing, GitHub's Copilot X, which is powered by ChatGPT4 is still forthcoming, and promises to enable developers with better support for code completions, PR reviews and auto-generated descriptions and the like.

According to GitHub's own product / landing / teaser page, they're still figuring out how to to package it all and how much it will cost. Given my experience with ChatGPT4 itself, I expect that Copilot X will perform a lot better across the board on developer wofkflow tasks including code completion.

I've had access to Copilot "original" as an open source developer for several months now, and had installed it on Neovim across a couple development machines. I was uniformly underwhelmed by its performance. The common wisdom these days appears to go: let Copilot fill in the boilerplate for you.

And indeed, in my experience, Copilot required me to fill in essentially everything else - a large comment for context, the function signature and the initial lines of code, perhaps even the return values. And then it could save me a bit of time by filling in some repetitive and obvious code I otherwise would have had to type out myself.

But most of the time, I was just ignoring Copilot suggestions as I went about my development tasks mostly unaided.

## How is Codeium different?

Codeium is free, forever, for individuals, such as yours truly. Here's my experience with Codeium in a nutshell. I signed up for free, and followed their installation guide to get the Codeium plugin working in Neovim.

However, because I'm currently running AstroNvim, which is an open-source project that provides an IDE-like experience out of the box, it didn't work initially for me, and I had to do some spelunking and tinkering to get it to work.

To be crystal clear, this is because I'm running something other than stock Neovim, intentionally, and because I like encountering these kinds of issues so that I can learn more. I don't fault Codeium for the fact that my installation was slightly more involved than just installing a new Neovim or vim plugin.

If you run more or less stock Vim or Neovim, I'd expect the Codeium installation instructions to more or less "just work" for you.

## How was the code-completion?

Really good - definitely better than GitHub Copilot, in my experience. I am not only getting excellent code completions that I can accept as I'm writing code, but Codeium is even correctly inferring what I'm trying to do when authoring markdown READMEs.

For example, the other day, I was writing up the README for my automations project, and when I started to write the prerequisites section, Codeium correctly inferred what I was going to do and wrote up all the links to the GitHub projects that are dependencies of my project.

In addition, Codeium keeps tabs on the completions you accept and in which languages, and as their site explains, Codeium comes with a wider array of languages that it supports out of the box than GitHub Copilot.

The bar chart is sort of fun to peak at, but, I thought, not very material, until I considered that in the span of a single hacking session, Codeium successfully assisted me with markdown, lua, go, shell  and Javascript. Not bad for a free service!

Read the article

Chat GPT-4 pointing out one of my bugs while I work

Over the years, I have embarked on [countless weekend side projects](https://github.com/zackproser) to explore new technologies, [refine my skills](https://www.youtube.com/@zackproser), and simply enjoy the creative process. As a Golang developer with a penchant for keyboard-driven development, I have learned to combine command line tools, IDEs, and text editors to maximize productivity and deliver results efficiently.

This weekend, I decided to see what, if any, speed boost I could unlock by weaving in ChatGPT-4 to my developer workflow and using it alongside GitHub Copilot, which I have been experimenting with for several months now. By the end of this post, you'll gain insights into the current state of these tools and how a Senior Software Engineer found them to be helpful, and in some cases, very helpful.

First off, I needed a simple project idea that could be tackled quickly. Enter [`sizeof`](https://github.com/zackproser/sizeof), the CLI project I developed during this experiment. Drawing from my experience with command line tools and efficient development practices, sizeof is designed to help developers build an intuitive understanding of the relative sizes of various inputs, strings, web pages, and files. It leverages the popular [charmbracelet bubbletea library](https://github.com/charmbracelet/bubbletea) to create an interactive TUI (Terminal User Interface) that displays this information in a visually appealing manner. The sizeof project is open-source and available at [https://github.com/zackproser/sizeof](https://github.com/zackproser/sizeof).

`sizeof` is really simple. Here's a quick demo gif that shows how it works:

One of the key strengths of large language models (LLMs) like ChatGPT-4 is their ability to generate reasonable starting points for various tasks. For instance, they excel at producing CircleCI configurations and READMEs, as they've essentially "seen" all possible variations. Leveraging this capability, I requested a starting point for the command line interface, which enabled me to dive into the development process quickly and start iterating on the initial code.

Here I am asking ChatGPT for the initial README, and because I am pretty sure it's seen most of my writing already, I can ask it to do its best to write it the way I would. As you can see in its response, it is going so far to include the HTML badges I tend to include in my open source projects. It also reflects my tendency to include a Table of Contents in any document I create!

It goes without saying, this is very useful and something I would love to be able to do in an even more streamlined manner. I still edited the final README by hand in Neovim and made all my own edits and tweaks, but I will have landed this side project much faster for not having to do everything from scratch each time.

Likewise, I essentially described the blog post and asked ChatGPT-4 for the first draft. By asking ChatGPT-4 to generate a first draft based on my desired talking points, I obtained a solid foundation to extend and edit. This greatly accelerated the writing process and allowed me to focus on refining the content.

My impressions of the quality of its responses is generally favorable. I experience about zero friction in explaining myself to ChatGPT 4 - and getting it to do exactly what I want. Because it is aware of the previous context, I can ask it insanely useful things like this:

### The sweet spot

generative, tedious tasks

ChatGPT 4 really shines when asked to perform tedious tasks that you would normally do yourself or hand to your copywriter when preparing a post for publishing online. Here I am asking ChatGPT 4 to effectively save me a chunk of time. Its first response was excellent so getting the copy I needed for LinkedIn and Twitter took me as long as it would have taken to effective type or speak my description of the task into being.

ChatGPT 4 generated: * The initial CLI scaffold, and then immediately tweaked it to include `bubbletea` in order to implement a TUI at my request * The initial README for the project * When asked, the mermaid.js diagram featured in the README of the [`sizeof` CLI project](https://github.com/zackproser/sizeof) * The first draft of this blog post * The LinkedIn post copy to announce the blog post * The Twiter post to announce the blog post

In all these cases, I ended up slightly modifying the results myself, and in all these cases, these are artifacts I can and have produced for myself, but there's no doubt generating these starting points saved me a lot of time.

Where is GitHub Copilot in all of this? Nowhere, essentially. For the most part, while working with ChatGPT4, there was nothing of value that GitHub Copilot had to add, and I find its code suggestions painfully clumsy and incorrect, which is more a testament to the speed at which these LLM models are currently being developed and advanced. Copilot X, which is forthcoming at the time of this writing and leverages ChatGPT 4 under the hood, will be of significantly more interest to me depending on its pricing scheme.

### The UX sticking points

Despite the clear advantages, there were some challenges when using ChatGPT-4. One of these was the developer experience. I want a seamless integration of this, and likely, a bunch of other current and forthcoming models right in my terminal and likely right in Neovim.

While open-source developers have created an impressive array of neovim and vim plugins, as well as experimental web applications that integrate ChatGPT into development workflows, achieving a first-class developer experience remains elusive, at least for me.

I have found a couple of open source developers putting forth some really awesome tools very quickly. Here are some of the best things I have found so far, which is by no means exhaustive:

[YakGPT](https://github.com/yakgpt/yakgpt)

Allows you to run a local server that hits the OpenAI API directly (bypassing their UI) and allowing you to do speech to text and text to speech. It's the closest thing to hands

free ChatGPT that I've seen so far.

[ChatGPT.nvim](https://github.com/jackMort/ChatGPT.nvim)

ChatGPT in Neovim

Context switching is the current main friction point I experienced during my experiment. Using ChatGPT-4 effectively required a fair amount of transitioning between my terminal driven development environment and the web browser for a clunky, often buggy UI experience a la Open AI's native web client. As someone who strives for optimized workflows and minimized friction, I found this cumbersome and disruptive to my work. What I really want is close to what YakGPT is already making possible: I want a super-intelligent daemon that I can task with background research or questions that require massive information digestion, or tedious things like figuring out a valid starting point for a CircleCI configuration for my new repository.

It seems I haven not yet discovered the magic key to integrate these tools seamlessly into my workflow, but at the rate things are advancing and given the amount of attention this space is currently getting, I expect this to continue to evolve very rapidly. I can imagine the workflow that at this point is not far off, where I could extend my own capabilities with several AI agents or models. I have not yet found the Neovim plugin that I want to roll forward with and tweak to my liking.

In several of the neovim plugins for ChatGPT I experimented with, I have noticed issues with ChatGPT API status needing to be reflected somehow within the terminal or curent buffer: essentially, signaling to the user that the ChatGPT plugin is not dead, just waiting on data. I fully expect all of these minor UX quirks to dissipate rapidly, and I expect to be leveraging some kind of LLM model regularly within my personal workflow in the months to come.

Finally, the more that I came to see what I could accomplish with a streamlined LLM experience in my preferred code editor, the more that I realized I am probably going to want some kind of AI interface in a variety of different contexts. As a developer and writer, I use various applications, such as [Obsidian](https://obsidian.md) for building my second brain.

Leveraging my experience in combining different tools, I am now eager to see similar AI-powered interfaces integrated into these other contexts to further streamline my productivity and creative processes. I wonder if we will end up wanting or seeing "AI wallets" or model multiplexers that allow you to securely or privately host a shared backend or database? If I want to run ChatGPT 4 in my terminal for coding, but also in my browser to help me edit text, and then later on my phone, how would I ideally share context amongst those different access points?

proser

typical

it

in

with

son

I've been working in intense tech startups for the past {RenderNumYearsExperience()} years. This is what keeps me healthy and somewhat sane.

The timeline below describes an ideal workday. Notice there's a good split between the needs of my body and mind and the needs of my employer. After 5 or 6PM, it's family time.

Beneath the timeline, I explain each phase and why works for me.

## Wake up early

I tend to be an early riser, but with age and additional responsibilities it's no longer a given that I'll spring out of bed at 5AM. I set a smart wake alarm on my fitbit which attempts to rouse me when I'm already in light sleep as close to my target alarm time as possible.

The more time I give myself in the morning for what is important to me, the less resentful I end up becoming even if the workday is intense and stressful.

For the past two jobs now I've used this time to read, sit in the sun, meditate, drink my coffee, and hack on stuff that I care about like my [many side projects](https://github.com/zackproser).

## Get sunlight

This helps me feel alert and gets my circadian cycle on track.

## Vipassana meditation

I sit with my eyes closed, noticing and labeling gently: inhaling, exhaling, thinking, hearing, feeling, hunger, pain, fear, thinking, etc.

## Metta meditation

I generate feelings of loving kindness for myself, visualizing myself feeling safe, healthy, happy and living with ease. This one I may do alongside a YouTube video. [Manoj Dias of Open has a great one](https://www.youtube.com/watch?v=vj0JDwQLof4).

## Coffee and fun

Some days, I'll ship a personal blog post, finish adding a feature to one of my side projects, read a book, or work on something that is otherwise important to me creatively.

## First block of work and meetings

Depending on the day, I'll have more or less focus time or meetings. Sometimes I block out my focus time on my work calendar to help others be aware of what I'm up to and to keep myself focused.

I'll do open-source work, write blog posts, create videos, attend meetings, or even do performance analysis on systems and configure a bunch of alerting services to serve as an SRE in a pinch, in my current role as a staff developer advocate at [Pinecone.io](https://www.pinecone.io).

I work until noon or 1pm before stopping to break my fast.

## Break my fast

I eat between the hours of noon and 8pm. This is the form of intermittent fasting that best works for me.

A few years ago, a blood panel showed some numbers indicating I was heading toward a metabolic syndrome I had no interest in acquiring, so I follow this protocol and eat mostly vegan but sometimes vegetarian (meaning I'll have cheese in very sparing amounts occasionally).

Sometimes I'll eat fish and sometimes I'll even eat chicken, but for the most part I eat vegan. In about 3 months of doing this, an updated blood panel showed I had completely reversed my metabolic issues. In general, I try to follow Michael Pollen's succinct advice: "Eat food. Not too much. Mostly plants".

## Long walk

I've reviewed the daily habits of a slew of famous creatives from the past, from sober scientists to famously drug-using artists and every combination in between. One thing that was common amongst most of them is that they took two or more longer walks during the day.

I try to do the same. I find that walking is especially helpful if I've been stuck on something for a while or if I find myself arm-wrestling some code, repository or technology that won't cooperate the way I initially thought it should.

It's usually within the first 20 minutes of the walk that I realize what the issue is or at least come up with several fresh avenues of inquiry to attempt when I return, plus I get oxygenated and usually find myself in a better mood when I get back.

I carry my son in my arms as I walk, talking to him along the way.

## Ice bath

This is from Wim Hof, whose [breathing exercises](https://www.youtube.com/watch?v=tybOi4hjZFQ) I also found helpful. I started doing cold showers every morning and tolerated them well and found they gave me a surge in energy and focused attention, so I ended up incrementally stepping it up toward regular ice baths.

First I bought an inflatable ice bath off Amazon and would occasionally go to the store and pick up 8 bags of ice and dump them into a tub full of hose water. I'd get into the bath for 20 minutes, use the same bluetooth mask I use for sleep and play a 20 minute yoga nidra recording.

The more I did this, the more I found that ice baths were for me. They not only boosted my energy and focus but also quieted my "monkey mind" as effectively as a deep meditative state that normally takes me more than 20 minutes to reach.

According to  [Andrew Huberman](https://www.hubermanlab.com/), the Stanford professor of neurology and opthamology who runs his own popular podcast, cold exposure of this kind can increase your available dopamine levels by 2x, which is similar to what cocaine would do, but for 6 continuous hours.

I've never tried cocaine so I can't confirm this from experience, but I can say that when I get out of a 20 minute ice bath I'm less mentally scattered and I feel like I have plenty of energy to knock out the remainder of my workday.

Now, I produce my own ice with a small ice machine and silicon molds I fill with hose water and then transfer into a small ice chest.

## Long walk at end of day and family time

Usually, working remotely allows me to be present with my family and to end work for the day between 4 and 6pm depending on what's going on. We like to take a long walk together before returning to settle in for the night.

## Sleep

I try to get to sleep around 11pm but that tends to be aspirational. I use the manta bluetooth sleep mask because it helps me stay asleep longer in the morning as I've found I'm very sensitive to any light.

I connect it to Spotify and play a deep sleep playlist without ads that is 16 hours long. I turn on do not disturb on my phone. Sometimes if my mind is still active I'll do breath counting or other breathing exercises to slow down.

---

This was the third article I published while working at Pinecone:

Read article

## If you came here from Hacker News...

Then I've learned that I need to explicitly spell out in the beginning of the post that this article is partly facetious.

|   Phrase said under stress|  Real meaning | |---|---| | Thanks for the feedback. |  Fuck you. | |   Thanks for the feedback! (with an explanation point) |   Die in a slow-moving, low-temperature and ultimately avoidable fire.| | That's interesting. |  You are unburdened by talent. | | Could you please file an issue? | Swim into a boat motor. Face-first, but get your arms in there, too. | | Like I said... | Clean the shit out of your ears. | | Let's table this for the time being. We've got N minutes left and a lot of agenda items remaining. | I'm sick of hearing your voice. Shut the fuck up. Your time management is even worse than your hygiene. | | How about we do this...| I'm tired of waiting for you to get it, so now I'm going to distract you with a false compromise. | | Sounds good | I have zero faith in your ability to deliver. None. So I'm passive agressively pointing to the fact that it only sounds good, because it's never going to be. | | Let me know if I can do anything to help | This is completely on you and I don't want to hear about it again. | | Let's take some action items. | I can't understand the power dynamics on this Zoom call yet but I'm incapable of contributing to the problems we're discussing here | | \, can you take an action item? | I'm a Muppet missing the adult hand that goes up my ass and tells me what to say.

I can tell you're all starting to catch on, so in my low-grade panic I'm going to try to distract all of you while I continue to wait around around for the adult to show up...| | NIT: I prefer a more \ coding style...| I'm finally secure enough in this institution to enforce my father-shaped emotional void upon you and our codebase. I never learned to wash my ass properly. | | Great idea, \! | You could have kept my entire salary plus benefits and skipped hiring me with no impact whatsoever on final outcomes or team strength. | | Great idea, \!| Your idea is now my idea. I'm going to start pitching it as my original work starting in my next meeting beginning in 10 minutes.| |Happy to help!|I'm updating my resume.| | I'll take a look | Let me Google that for you, you incompetent ninny. I'm also positive you got a larger equity grant than I did. | | You're one of our top performers | We're underpaying you. |

## Introduction

Here's a comprehensive comparison AI-assisted developer tools, including code autocompletion, intelligent terminals/shells, and video editing tools. Reviews are linked when available.

## Table of Contents

1. [Code Autocompletion](#code-autocompletion) 2. [Intelligent Terminals / Shells](#intelligent-terminals-/-shells) 3. [Video Editing](#video-editing) 4. [Mutation Testing](#mutation-testing) 5. [Enhanced IDE](#enhanced-ide)

## Tools and reviews

| Tool | Category | Review | Homepage |------|------|------|------| | GitHub Copilot | Code Autocompletion | 📖 Review | https://github.com/features/copilot | | Warp | Intelligent Terminals / Shells | 📖 Review | https://www.warp.dev/ | | Descript | Video Editing | Coming soon | https://www.descript.com/ | | Codeium | Code Autocompletion | 📖 Review | https://codeium.com | | Kapwing | Video Editing | Coming soon | https://www.kapwing.com | | Aider | Intelligent Terminals / Shells | Coming soon | https://aider.chat | | Cody | Code Autocompletion | Coming soon | https://sourcegraph.com/cody | | Mods | Enhanced IDE | Coming soon | https://github.com/charmbracelet/mods | | Zed | Code Autocompletion | Coming soon | https://zed.dev | | Mutahunter | Mutation Testing | Coming soon

| https://github.com/codeintegrity-ai/mutahunter | | Cursor | Code Autocompletion | 📖 Review | https://cursor.sh | | OpusClip | Video Editing | Coming soon | https://www.opus.pro | | Tabnine | Code Autocompletion | 📖 Review | https://www.tabnine.com | | MutableAI | Code Autocompletion | 📖 Review | https://mutable.ai | | CodiumAI | Code Autocompletion | Coming soon | https://www.codium.ai | | Grit.io | Code Autocompletion | 📖 Review | https://www.grit.io | | Adrenaline AI | Code Autocompletion | Coming soon | https://useadrenaline.com | | Amazon CodeWhisperer | Code Autocompletion | Coming soon | https://aws.amazon.com/codewhisperer/ | | Figstack | Code Autocompletion | Coming soon | https://www.figstack.com |

## Code Autocompletion

Code autocompletion tools save developers time and effort by automatically suggesting and completing code snippets based on the context of the code being written.

### Open source

| Tool | Client | Backend | Model | |------|-|-|-| | GitHub Copilot | ❌ | ❌ | ❌ | | Codeium | ✅ | ❌ | ❌ | | Cody | ✅ | ❌ | ❌ | | Zed | ✅ | ❌ | ❌ | | Cursor | ❌ | ❌ | ❌ | | Tabnine | ❌ | ❌ | ❌ | | MutableAI | ❌ | ❌ | ❌ | | CodiumAI | ❌ | ❌ | ❌ | | Grit.io | ❌ | ❌ | ❌ | | Adrenaline AI | ❌ | ❌ | ❌ | | Amazon CodeWhisperer | ❌ | ❌ | ❌ | | Figstack | ❌ | ❌ | ❌ |

,

### Ide support

| Tool | Vs Code | Jetbrains | Neovim | Visual Studio | Vim | Emacs | Intellij | |------|-|-|-|-|-|-|-| | GitHub Copilot | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | | Codeium | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | | Cody | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | Zed | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Cursor | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Tabnine | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | MutableAI | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | CodiumAI | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | | Grit.io | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | | Adrenaline AI | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | | Amazon CodeWhisperer | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | | Figstack | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |

,

### Pricing

| Tool | Model | Tiers | |------|-------|------| | GitHub Copilot | subscription | Individual: $10 per month, Team: $100 per month | | Codeium | subscription | Individual: $10 per month, Team: $100 per month | | Cody | free | Standard: Free, Pro: Subscription based - contact for pricing | | Zed | subscription | Free: Free, Pro: $20 per month, Team: $50 per month | | Cursor | free | Hobby: Free, Pro: $20 per month, Business: $40 per month | | Tabnine | subscription | Basic: Free, Pro: $12 per month per seat, Enterprise: $39 per month per seat | | MutableAI | subscription | Basic: $2 per month and up, Premium: $15 per month, Enterprise: Custom | | CodiumAI | subscription | Developer: Free, Teams: $19 per user/month, Enterprise: Let's talk | | Grit.io | subscription | CLI: Free, Team: $39 / contributor, Enterprise: Book a demo | | Adrenaline AI | subscription | Free Trial: Free, limited time, Pro: $10 per month | | Amazon CodeWhisperer | free | Individual: Free | | Figstack | freemium | Free: Free, Starter: $9 per month, Unlimited: $29 per month |

,

### Free tier

| Tool | Free tier | |------|------| | GitHub Copilot | ❌ | | Codeium | ✅ | | Cody | ✅ | | Zed | ✅ | | Cursor | ✅ | | Tabnine | ✅ | | MutableAI | ❌ | | CodiumAI | ✅ | | Grit.io | ✅ | | Adrenaline AI | ✅ | | Amazon CodeWhisperer | ✅ | | Figstack | ✅ |

,

### Chat interface

| Tool | Chat interface | |------|------| | GitHub Copilot | ❌ | | Codeium | ✅ | | Cody | ✅ | | Zed | ✅ | | Cursor | ✅ | | Tabnine | ✅ | | MutableAI | ❌ | | CodiumAI | ✅ | | Grit.io | ❌ | | Adrenaline AI | ❌ | | Amazon CodeWhisperer | ✅ | | Figstack | ❌ |

,

### Creator

| Tool | Creator | |------|------| | GitHub Copilot | GitHub | | Codeium | Codeium | | Cody | Sourcegraph | | Zed | Zed Industries | | Cursor | Anysphere | | Tabnine | Codota | | MutableAI | MutableAI Corp | | CodiumAI | CodiumAI | | Grit.io | Grit Inc | | Adrenaline AI | Adrenaline Tech | | Amazon CodeWhisperer | Amazon Web Services | | Figstack | Figstack Inc |

,

### Language support

| Tool | Python | Javascript | Java | Cpp | |------|-|-|-|-| | GitHub Copilot | ✅ | ✅ | ✅ | ✅ | | Codeium | ✅ | ✅ | ✅ | ✅ | | Cody | ✅ | ✅ | ✅ | ✅ | | Zed | ✅ | ✅ | ✅ | ✅ | | Cursor | ✅ | ✅ | ❌ | ❌ | | Tabnine | ✅ | ✅ | ✅ | ✅ | | MutableAI | ✅ | ✅ | ❌ | ❌ | | CodiumAI | ✅ | ✅ | ✅ | ✅ | | Grit.io | ✅ | ✅ | ✅ | ❌ | | Adrenaline AI | ✅ | ✅ | ✅ | ✅ | | Amazon CodeWhisperer | ✅ | ✅ | ✅ | ❌ | | Figstack | ✅ | ✅ | ✅ | ✅ |

,

### Supports local model

| Tool | Supports local model | |------|------| | GitHub Copilot | ❌ | | Codeium | ❌ | | Cody | ❌ | | Zed | ❌ | | Cursor | ✅ | | Tabnine | ❌ | | MutableAI | ❌ | | CodiumAI | ❌ | | Grit.io | ❌ | | Adrenaline AI | ❌ | | Amazon CodeWhisperer | ❌ | | Figstack | ❌ |

,

### Supports offline use

| Tool | Supports offline use | |------|------| | GitHub Copilot | ❌ | | Codeium | ❌ | | Cody | ❌ | | Zed | ❌ | | Cursor | ✅ | | Tabnine | ❌ | | MutableAI | ❌ | | CodiumAI | ❌ | | Grit.io | ❌ | | Adrenaline AI | ❌ | | Amazon CodeWhisperer | ❌ | | Figstack | ❌ |

## Intelligent Terminals / Shells

Intelligent terminals and shells enhance the command-line experience with features like command completion, advanced history search, and AI-powered assistance.

### Open source

| Tool | Client | Backend | Model | |------|-|-|-| | Warp | ❌ | ❌ | ❌ | | Aider | ❌ | ❌ | ❌ |

,

### Pricing

| Tool | Model | Tiers | |------|-------|------| | Warp | subscription | Free: Free, Pro: $15 per month, Team: $22 per month, Enterprise: Custom | | Aider | subscription | Free: Free and open source |

,

### Free tier

| Tool | Free tier | |------|------| | Warp | ✅ | | Aider | ✅ |

,

### Chat interface

| Tool | Chat interface | |------|------| | Warp | ✅ | | Aider | ✅ |

,

### Command completion

| Tool | Command completion | |------|------| | Warp | ✅ | | Aider | ✅ |

,

### Advanced history

| Tool | Advanced history | |------|------| | Warp | ✅ | | Aider | ❌ |

,

### Supports local model

| Tool | Supports local model | |------|------| | Warp | ❌ | | Aider | ❌ |

,

### Supports offline use

| Tool | Supports offline use | |------|------| | Warp | ❌ | | Aider | ❌ |

## Video Editing

AI-assisted video editing tools simplify the video editing process by offering features like automatic transcription, editing via transcription, and intelligent suggestions.

### Open source

| Tool | Client | Backend | Model | |------|-|-|-| | Descript | ❌ | ❌ | ❌ | | Kapwing | ❌ | ❌ | ❌ | | OpusClip | ❌ | ❌ | ❌ |

,

### Pricing

| Tool | Model | Tiers | |------|-------|------| | Descript | subscription | Creator: $12 per month, Pro: $24 per month | | Kapwing | subscription | Free: Free, Pro: $16 per month, Business: $50 per month, Enterprise: Custom | | OpusClip | subscription | Free: Free, limited features, Starter: $15 per month, Pro: $29 per month |

,

### Free tier

| Tool | Free tier | |------|------| | Descript | ❌ | | Kapwing | ✅ | | OpusClip | ✅ |

,

### Works in browser

| Tool | Works in browser | |------|------| | Descript | ✅ | | Kapwing | ✅ | | OpusClip | ✅ |

,

### Supports autotranscribe

| Tool | Supports autotranscribe | |------|------| | Descript | ✅ | | Kapwing | ✅ | | OpusClip | ✅ |

,

### Edit via transcription

| Tool | Edit via transcription | |------|------| | Descript | ✅ | | Kapwing | ✅ | | OpusClip | ✅ |

## Mutation Testing

Mutation testing tools enhance software quality by injecting faults into the codebase and verifying if the test cases can detect these faults.

### Open source

| Tool | Client | Backend | Model | |------|-|-|-| | Mutahunter | ✅ | ✅ | ❌ |

,

### Language support

| Tool | Python | Javascript | Java | Cpp | |------|-|-|-|-| | Mutahunter | ✅ | ✅ | ✅ | ❌ |

,

### Supports local model

| Tool | Supports local model | |------|------| | Mutahunter | ✅ |

,

### Supports offline use

| Tool | Supports offline use | |------|------| | Mutahunter | ❌ |

,

### Pricing

| Tool | Model | Tiers | |------|-------|------| | Mutahunter | free | Free: Free |

## Enhanced IDE

Enhanced IDE tools provide advanced features and integrations to improve the development experience within integrated development environments.

## Remember to bookmark and share

This page will be updated regularly with new information, revisions and enhancements. Be sure to share it and check back frequently.

---

developer

your

own

## Table of contents

## Building your own tools

One of my favorite things about being a developer is creating my own tools.

In some cases, this means building a tool that only I can access - one that supports login with GitHub but only allows `zackproser` through.

Most recently, I've been building [Panthalia](https://github.com/zackproser), but this post is about the benefits of building your own tools as a developer.

## Building tools is good practice

You're still building software and picking up new experiences when you work on pet projects.

You're more free when you're hacking on something for yourself.

You can feel free to try out a new framework, language or architecture.

## Eating your own dogdood makes you sensitive to dogfood taste

When you are both a tool's creator and primary user, you quickly pick up on which UX patterns suck.

Having a janky workflow around saving your content will eventually bother you enough that you'll fix it and make it more robust.

I believe this helps you to have more compassion for the experience of end users when you're building something that is intended for wider consumption.

## It's fun

Especially as the functionality you originally desired begins to take shape.

## It's a singular feeling

To one day realize that the tool you've been hacking on for a while has finally turned a corner and is able to solve the original use case you set out to solve.

## Tool development can be lucrative

Some developers polish relentlessly until their project starts getting real adoption and becomes a real revenue stream for them.

But this isn't the only way that building your own tools can make you money.

The skills and experience you pick up while hacking are applicable to full-time jobs, part-time gigs and contracts. In my experience, the best developers are those who are constantly scratching their own technical itches by trying things out in code.

A little bit down the road, when you're using at your day job what you learned in your spare personal hacking time, you'll see the value of contantly building things for practice.

---

## Table of contents

In the rapidly evolving world of artificial intelligence (AI) and machine learning, there's a concept that's revolutionizing the way machines understand and process data: embeddings. Embeddings, also known as vectors, are floating-point numerical representations of the "features" of a given piece of data. These powerful tools allow machines to achieve a granular "understanding" of the data we provide, enabling them to process and analyze it in ways that were previously impossible. In this comprehensive guide, we'll explore the basics of embeddings, their history, and how they're revolutionizing various fields.

## Extracting Features with Embedding Models

At the heart of embeddings lies the process of feature extraction. When we talk about "features" in this context, we're referring to the key characteristics or attributes of the data that we want our machine learning models to learn and understand. For example, in the case of natural language data (like text), features might include the semantic meaning of words, the syntactic structure of sentences, or the overall sentiment of a document.

To obtain embeddings, you feed your data to an embedding model, which uses a neural network to extract these relevant features. The neural network learns to map the input data to a high-dimensional vector space, where each dimension represents a specific feature. The resulting vectors, or embeddings, capture the essential information about the input data in a compact, numerical format that machines can easily process and analyze.

There are various embedding models available, ranging from state-of-the-art models developed by leading AI research organizations like OpenAI and Google, to open-source alternatives like Word2Vec and GloVe. Each model has its own unique architecture and training approach, but they all share the common goal of learning meaningful, dense representations of data.

## A Detailed Example

To better understand how embeddings work in practice, let's consider a concrete example. Suppose we have the following input data: "The quick brown fox jumps over the lazy dog"

When we pass this string of natural language into an embedding model, the model uses its learned neural network to analyze the text and extract its key features. The output of this process is a dense vector, or embedding, that looks something like this:

Each value in this vector is a floating-point number, typically ranging from -1 to 1. These numbers represent the presence or absence of specific features in the input data. For example, one dimension of the vector might correspond to the concept of "speed," while another might represent "animal." The embedding model learns to assign higher values to dimensions that are more strongly associated with the input data, and lower values to dimensions that are less relevant.

So, in our example, the embedding vector might have a high value in the "speed" dimension (capturing the concept of "quick"), a moderate value in the "animal" dimension (representing "fox" and "dog"), and relatively low values in dimensions that are less relevant to the input text (like "technology" or "politics").

 High dimensional vector space - each point is a vector and their distance from one another represents their similarity.

The true power of embeddings lies in their ability to capture complex relationships and similarities between different pieces of data. By representing data as dense vectors in a high-dimensional space, embedding models can learn to group similar items together and separate dissimilar items. This enables machines to perform tasks like semantic similarity analysis, clustering, and classification with remarkable accuracy and efficiency.

## Applications of Embeddings

The potential applications of embeddings are vast and diverse, spanning across multiple domains and industries. Some of the most prominent areas where embeddings are making a significant impact include:

Natural Language Processing (NLP) In the field of NLP, embeddings have become an essential tool for a wide range of tasks, such as:

### Text classification

Embedding models can learn to represent text documents as dense vectors, capturing their key semantic features. These vectors can then be used as input to machine learning classifiers, enabling them to automatically categorize text into predefined categories (like "spam" vs. "not spam," or "positive" vs. "negative" sentiment).

### Sentiment analysis

By learning to map words and phrases to sentiment-specific embeddings, models can accurately gauge the emotional tone and opinion expressed in a piece of text. This has powerful applications in areas like social media monitoring, customer feedback analysis, and brand reputation management.

### Named entity recognition

Embeddings can help models identify and extract named entities (like people, places, organizations, etc.) from unstructured text data. By learning entity-specific embeddings, models can disambiguate between different entities with similar names and accurately label them in context.

### Machine translation

Embedding models have revolutionized the field of machine translation by enabling models to learn deep, semantic representations of words and phrases across different languages. By mapping words in the source and target languages to a shared embedding space, translation models can capture complex linguistic relationships and produce more accurate, fluent translations.

### Image and Video Analysis

Embeddings are not limited to textual data – they can also be applied to visual data like images and videos. Some key applications in this domain include:

#### Object detection

By learning to map image regions to object-specific embeddings, models can accurately locate and classify objects within an image. This has important applications in areas like autonomous vehicles, surveillance systems, and robotics.

#### Face recognition

Embedding models can learn to represent faces as unique, high-dimensional vectors, capturing key facial features and enabling accurate face identification and verification. This technology is used in a variety of settings, from mobile device unlocking to law enforcement and security systems.

#### Scene understanding

By learning to embed entire images or video frames, models can gain a holistic understanding of the visual scene, including object relationships, spatial layouts, and contextual information. This enables applications like image captioning, visual question answering, and video summarization.

### Video recommendation

Embeddings can capture the semantic content and style of videos, allowing recommendation systems to suggest similar or related videos to users based on their viewing history and preferences.

### Recommendation Systems

Embeddings play a crucial role in modern recommendation systems, which aim to provide personalized content and product suggestions to users. Some key applications include:

### Product recommendations

By learning to embed user preferences and product features into a shared vector space, recommendation models can identify meaningful similarities and suggest relevant products to users based on their past interactions and behavior.

### Content personalization

Embedding models can learn to represent user profiles and content items (like articles, videos, or songs) as dense vectors, enabling personalized content ranking and filtering based on individual user preferences.

### Collaborative filtering

Embeddings enable collaborative filtering approaches, where user and item embeddings are learned jointly to capture user-item interactions. This allows models to make accurate recommendations based on the preferences of similar users, without requiring explicit feature engineering.

### Anomaly Detection

Embeddings can also be used to identify unusual or anomalous patterns in data, making them a valuable tool for tasks like:

### Fraud detection

By learning normal behavior patterns and embedding them as reference vectors, models can flag transactions or activities that deviate significantly from the norm, potentially indicating fraudulent behavior.

### Intrusion detection

In the context of network security, embeddings can help models learn the typical patterns of network traffic and user behavior, enabling them to detect and alert on anomalous activities that may signal a security breach or intrusion attempt.

### System health monitoring

Embeddings can capture the normal operating conditions of complex systems (like industrial equipment or software applications), allowing models to identify deviations or anomalies that may indicate potential failures or performance issues.

Leveraging the power of embeddings, developers and data scientists can build more intelligent and efficient systems that can better understand and process complex data across a wide range of domains and applications.

## A Brief History of Embeddings

The concept of embeddings has its roots in the field of natural language processing, where researchers have long sought to represent words and phrases in a way that captures their semantic meaning and relationships. One of the earliest and most influential works in this area was the Word2Vec model, introduced by Tomas Mikolov and his colleagues at Google in 2013.

Word2Vec revolutionized NLP by demonstrating that neural networks could be trained to produce dense vector representations of words, capturing their semantic similarities and relationships in a highly efficient and scalable manner. The key insight behind Word2Vec was that the meaning of a word could be inferred from its context – that is, the words that typically appear around it in a sentence or document.

By training a shallow neural network to predict the context words given a target word (or vice versa), Word2Vec was able to learn highly meaningful word embeddings that captured semantic relationships like synonymy, antonymy, and analogy. For example, the embedding for the word "king" would be more similar to the embedding for "queen" than to the embedding for "car," reflecting their semantic relatedness.

The success of Word2Vec sparked a wave of research into neural embedding models, leading to the development of more advanced techniques like GloVe (Global Vectors for Word Representation) and FastText. These models built upon the core ideas of Word2Vec, incorporating additional information like global word co-occurrence statistics and subword information to further improve the quality and robustness of the learned embeddings.

In recent years, the power of embeddings has been further amplified by the advent of deep learning and the availability of large-scale training data. State-of-the-art embedding models like BERT (Bidirectional Encoder Representations from Transformers) and GPT (Generative Pre-trained Transformer) have pushed the boundaries of what's possible with neural embeddings, achieving remarkable results on a wide range of NLP tasks like question answering, text summarization, and sentiment analysis.

At the same time, the success of embeddings in NLP has inspired researchers to apply similar techniques to other domains, such as computer vision and recommender systems. This has given rise to new types of embedding models, like CNN-based image embeddings and graph embeddings for social networks, which have opened up exciting new possibilities for AI and machine learning.

As the field of AI continues to evolve at a rapid pace, embeddings will undoubtedly play an increasingly important role in enabling machines to understand and process complex data across a wide range of domains and applications. By providing a powerful and flexible framework for representing and analyzing data, embeddings are poised to unlock new frontiers in artificial intelligence and transform the way we interact with technology.

## The Future of Embeddings

As we look to the future, it's clear that embeddings will continue to play a central role in the development of more intelligent and capable AI systems. Some of the key areas where we can expect to see significant advancements in the coming years include:

### Multimodal Embeddings

One of the most exciting frontiers in embedding research is the development of multimodal embedding models that can learn joint representations across different data modalities, such as text, images, audio, and video. By combining information from multiple sources, these models can potentially achieve a more holistic and nuanced understanding of the world, enabling new applications like cross-modal retrieval, multimodal dialogue systems, and creative content generation.

### Domain

Specific Embeddings

While general-purpose embedding models like Word2Vec and BERT have proven highly effective across a wide range of tasks and domains, there is growing interest in developing more specialized embedding models that are tailored to the unique characteristics and requirements of particular industries or applications. For example, a medical embedding model might be trained on a large corpus of clinical notes and medical literature, learning to capture the complex relationships between diseases, symptoms, treatments, and outcomes. Similarly, a financial embedding model could be trained on news articles, company reports, and stock market data to identify key trends, risks, and opportunities in the financial markets.

By leveraging domain-specific knowledge and training data, these specialized embedding models have the potential to achieve even higher levels of accuracy and utility compared to their general-purpose counterparts.

### Explainable Embeddings

As AI systems become increasingly complex and opaque, there is a growing need for embedding models that are more interpretable and explainable. While the high-dimensional vectors learned by current embedding models can capture rich semantic information, they are often difficult for humans to understand or reason about directly.

To address this challenge, researchers are exploring new techniques for learning more interpretable and transparent embeddings, such as sparse embeddings that rely on a smaller number of active dimensions, or factorized embeddings that decompose the learned representations into more meaningful and human-understandable components. By providing more insight into how the embedding model is making its decisions and predictions, these techniques can help to build greater trust and accountability in AI systems, and enable new forms of human-machine collaboration and interaction.

### Efficient Embedding Learning

Another key challenge in the development of embedding models is the computational cost and complexity of training them on large-scale datasets. As the size and diversity of available data continue to grow, there is a need for more efficient and scalable methods for learning high-quality embeddings with limited computational resources and training time.

To this end, researchers are exploring techniques like few-shot learning, meta-learning, and transfer learning, which aim to leverage prior knowledge and pre-trained models to accelerate the learning process and reduce the amount of labeled data required. By enabling the rapid development and deployment of embedding models in new domains and applications, these techniques could greatly expand the impact and accessibility of AI and machine learning in the real world.

## Learning More About Embeddings

If you're excited about the potential of embeddings and want to dive deeper into this fascinating field, there are many excellent resources available to help you get started. Here are a few recommended readings and educational materials:

### Research Papers

["Efficient Estimation of Word Representations in Vector Space" by Tomas Mikolov, et al. (Word2Vec)](https://arxiv.org/abs/1301.3781)

["GloVe: Global Vectors for Word Representation" by Jeffrey Pennington, et al. ](https://nlp.stanford.edu/pubs/glove.pdf)

["BERT: Pre

training of Deep Bidirectional Transformers for Language Understanding" by Jacob Devlin, et al. ](https://arxiv.org/abs/1810.04805)

### Books

["Deep Learning" by Ian Goodfellow, Yoshua Bengio, and Aaron Courville (chapters on representation learning and embeddings)](https://www.deeplearningbook.org/)

["Mining of Massive Datasets" by Jure Leskovec, Anand Rajaraman, and Jeff Ullman (chapters on dimensionality reduction and embeddings)](http://www.mmds.org/)

### Online demos

[Embeddings demo](/demos/embeddings)

By investing time in learning about embeddings and experimenting with different techniques and models, you'll be well-equipped to harness their power in your own projects and contribute to the exciting field of AI and machine learning.

## Wrapping Up

Embeddings are a fundamental building block of modern artificial intelligence, enabling machines to understand and reason about complex data in ways that were once thought impossible. By learning dense, continuous vector representations of the key features and relationships in data, embedding models provide a powerful framework for a wide range of AI applications, from natural language processing and computer vision to recommendation systems and anomaly detection.

As we've seen in this post, the concept of embeddings has a rich history and a bright future, with ongoing research pushing the boundaries of what's possible in terms of multimodal learning, domain specialization, interpretability, and efficiency.

mind

mapper

mind

mapper

python

data

too

python

github

exclude

mnist

neural

mnist

mind

mapper

test

## Table of contents

## Demo of the "MNIST mind-mapper" app in action

You can watch this short demo to see how the app works:

## Overview

I've been an application and infrastructure developer for the majority of my career, so I wanted to get hands-on with training and deploying a neural network.

I also wanted to wrap that trained neural network in a REST API, so that I could build a frontend that would allow folks to play with it, because interacting with something is more engaging than reading a text description of it.

I knew it would be important to go beyond the working neural net, because issues often arise at the seams, when you're fitting system components together.

This article details the steps I took and the many issues I encountered along the way to building and successfully deploying my original vision.

## Open source code

I open source most of my work, and this project is no exception:

## App flow diagram

Let's step through how the app works, end to end:

The frontend exposes a small drawable canvas, which the user scribbles on.

On a regular interval, the frontend captures what the user drew, using the `toDataURL` method:

This image is sent to the backend API, which wraps the trained neural network. The backend runs inference on the image, and returns the predicted digit, which the frontend displays.

## Step 1: Model training

The following code is all that's needed with pytorch to:

1. Define a simple neural network architecture 2. Load the MNIST dataset 3. Train the neural network 4. Save the model to disk so that it can be loaded and reused later

With the training complete, I wanted to quickly sanity check the trained network's performance on the intended task, so I created a simple FAST API server that exposes a `/test_images` route:

You can view the entire `main.py` file in [the repository](https://github.com/zackproser/pytorch-mnist/blob/main/main.py), but we'll examine the route itself here, which runs a self-test and shares the output as a PNG image like so:

Now I had a trained and working neural network and I was ready to deploy it. Here's where all the fun began.

## First challenge: Vercel's Python support is immature

Vercel's beta support for the Python runtime for use in backend functions is very exciting. I believe the ability to deploy a mixed app with a Next.js frontend and a Python backend has huge potential.

The Python ecosystem is rich with popular machine learning libraries, utilities and datasets. Meanwhile, JavaScript provides an excellent way to provide charts, graphs, data visualizations, games and other interactive representations of complex data models.

I'm personally very excited to build a ton of example applications with Python backends and TypeScript frontends on Vercel. But we're not quite there yet.

Unfortunately, the docs Vercel has for using their Python runtime are very sparse, the examples are light and most of the new concepts are not sufficiently explained. You have to read through the Python Vercel templates to understand how everything fits together.

Errors are also opaque. The showstopper for getting my Python backend deployed successfully on Vercel was an unintuitive error message: `data too long`.

I was pretty sure that pytorch and torchvision were likely blowing out the 4.5MB size limit on serverless functions, but there wasn't a great way to confirm this. My model/weights file was just under 400KB.

Things that you'd expect to be extensively documented, such as how to get around common bundling issues, or how to deploy a Python backend with PyTorch and a custom model, are also sorely needed.

You find extremely important details like this hiding out in 4 year old GitHub issue comment threads:

## Converting to ONNX and trying again

ONNX, (which stands for Open Neural Network Exchange) is a fascinating project that defines a common language for machine learning models, to allow Cloud providers and developers to more easily write and deploy models.

You can convert a PyTorch model to ONNX using the `torch.onnx.export` function. ONNX CAN sometimes reduce the size of the exported model, so I decided to give it a shot.

I also was happy to take any excuse to play around with ONNX after reading about it. After converting the model to the ONNX format, I tested everything again locally to ensure the app still functioned as intended. The potential size savings from ONNX did not end up making a difference in my case, and I got the same error.

## Finding a Python backend host in Modal.com

In researching platforms that host your Python code as cloud functions, I found modal.com which was lightweight and fast to set up.

I ended up converting the local version of my Fast API Python backend to a file I named `modal_function.py` which I then deployed like so:

`modal deploy modal_function.py`

Of course, my prefence would be for the backend to be hosted on Vercel, so that I could colocate the frontend and backend code and have a simpler deployment model, but modal.com ended up being exactly what I wanted in the absence of that.

Modal.com lets you sign up quickly, install their CLI and deploy your Python code as a serverless function or a deployed REST API, or an ephemeral testing endpoint. So far, it's been great.

There is the issue of cold starts, because Modal will spin down your container when your service is not receiving traffic, but overall I've been impressed with the developer experience.

[View and clone the project here](https://github.com/zackproser/teatutor)

In this post, I’ll do a deep-dive on my recently published open-source app, Tea Tutor. This application allows you to quickly define and serve colorful and interactive quizzes on any topic over SSH, and it was written using Charm’s Bubbletea and Wish libraries.

## What is Bubbletea and why should I care?

[Charm](https://charm.sh) has built a truly impressive suite of terminal user interface (TUI) libraries and tools. For Golang developers seeking to build rich applications that approach the interactivity of HTML in the terminal, solutions like Bubbletea, complimented by Glamour and Lipgloss, are some of the best options today.

## Introducing the Bubbletea blog post series

This article is the first in a series about building interactive terminal applications with Bubbletea. The next posts in the series are:

[Introducing the Tea Tutor program and my Bubbletea blog post series](https://medium.com/@zackproser/introducing

tea

tutor

a

quiz

service

you

connect

to

over

ssh

564efe963411)

This post

— A deep dive into the Tea Tutor program, including tips and tricks figured out

COMING SOON — How to serve your Bubbletea program over SSH with Wish

COMING SOON — Packaging your Bubbletea program using Infrastructure as Code techniques to make deploying it easier

COMING SOON — Future enhancements planned for Tea Tutor

## Why did I build this app?

My usual answer: for fun and practice.

I knew I wanted to build something non-trivial with Bubbletea. Ever since I stumbled across the Charm repositories, I was impressed with their examples and demos and the synergy between their open-source projects.

I also build a lot of command line interfaces (CLIs) in my day job as an infrastructure / automation / DevOps engineer, so I’m always keeping an eye out for better ways of doing things.

## Practice, practice, practice

My initial forays into working with Bubbletea were painful, as I regularly encountered crashes or couldn’t quite get things wired up and behaving the exact way I wanted.

In the end, I really just needed to fill the gaps in my own knowledge, and change my usual habits of approaching a Golang program until I was able to make reliable progress.

Anyone who has written or maintained highly evented Javascript applications, or used Backbone.js before, is likely going to find Bubbletea’s patterns familiar, and the Bubbletea maintainers have included two great tutorials, and are publishing content regularly across YouTube and Twitch.

My difficulty stemmed from my own incorrect mental models, and from finicky pieces like panics due to trying to access an out of bounds index in a slice — which can commonly occur when you’re navigating through some collection in your terminal UI (TUI). I’ll share some tips on how I made this behavior more reliable in my app a little later on.

>  Bubble Tea is based on the functional design paradigms of The Elm Architecture, which happens to work nicely with Go. It’s a delightful way to build applications.

I never doubted making it to the other side was worth it. Now that I have my head around the general operating model of a Bubbletea program, adding new functionality to my existing program is indeed more straightforward, easier to keep organized and yes, even, at times, delightful.

## Thinking through my desired experience

To begin with, I started thinking through the core app experience. I knew my core use case was to provide a very low friction, yet effective study aid for folks preparing to take an AWS certification exam.

I wanted anyone studying for AWS certifications, who finds themselves with a few spare minutes, to be able to fire off a simple

`ssh quiz..com`

command and immediately be served their own private instance of a familiar slide-deck-like experience that asks them multiple-choice AWS certification questions and records all their answers.

When the user submits their final answer, their score should be calculated by looking through all their submissions and comparing them to the correct answers. The user should get an easy to scan report showing them each question, their response and whether or not their response was correct.

This formed the kernel of my app idea.

## Modeling the data

Now that I knew what I wanted to build, it was time to figure out the data model.

The data model for questions is quite straightforward. Once I had done enough mocking by hardcoding a few questions as variables in main.go to be able to figure out the general flow I wanted for the UI, I knew I wanted to represent my questions in a separate YAML file that could be loaded at runtime.

There are a couple advantages to unlock by separating the data from the source code, out into a separate file:

You could imagine wanting to load different questions.yml files on different servers so that you could easily provision different subject

specific quiz servers

By having the core set of initial questions defined in a flat YAML file in the root of the project, anyone could come along and contribute to the project by opening a pull request adding a bunch of high quality questions across a variety of topics — even if that person weren’t necessarily a developer

It’s a lot easier to work on application / UI code that is agnostic about the actual data it is displaying. Otherwise, you end up codifying some of your actual data in your source code, making it more difficult, brittle and tedious to change in the future.

## Handling resize events like a pro

Bubbletea makes it easy to handle WindowSizeMsg events, which contain information about the size of the current terminal window. This is very handy not just for ensuring your app looks great even when a user resizes their terminal, but can also help you render custom experiences and even trigger re-paints as needed.

Here’s the WindowSizeMsg handler in my app. As you can see, we’re actually handling two different re-sizable entities in this case within our Update method:

We size the pager according to the latest width and height of the user’s terminal and. This pager is a scroll

able buffer where I store the contents of the user’s results printout, so that the user can easily scroll up and down through their test results

We set the width of the progress bar that shows the user how far along through the current quiz they are according to the latest width and height of the user’s terminal

## You can also use WindowSizeMsg to trigger UI re-paints

In the case of Tea Tutor, I render the user’s final score in a Viewport “Bubble”:

However, I was noticing an unfortunate bug whenever I ran my app over SSH and connected to it as a client:

Although everything worked perfectly if I just ran go run main.go , whenever I ran my app on a server and connected to it over SSH, my final results were rendered, but not at the correct full height and width of the user’s current terminal.

I knew I had logic in my Update function’s tea.WindowSizeMsg handler that would update the Viewport’s size based on the latest values of the user’s terminal size — so I really just wanted to trigger that logic just as the Viewport was being rendered for the user.

Therefore I decided to implement a separate tea.Cmd here, which is to say a function that returns a tea.Msg , to sleep for half a second and then use the latest terminal size values to render the user’s final report in a way that correctly takes up the full height and width of the current terminal.

It looks like this, and I return this command when the app reaches its displayingResults phase:

The delay is barely perceptible to the user, if at all, but the result is that the user’s final score report is rendered within the Viewport correctly — taking up the whole available height and width of the current terminal.

## Creating separate navigation functions

I mentioned above that, when I started working with Bubbletea, I ran into frequent crashes due to my mishandling of a slice data type that backed a collection the UI would iterate through.

For example, you can imagine rendering a simple list in a Bubbletea program, and allowing the user to move their “cursor” up and down the list to select an item.

Imagine the model’s cursor field is an int , and that it is incremented each time the user presses the up button or the letter k . Imagine that you have wired up the enter button to select the item in the backing slice at the index of whatever value cursor is currently set to.

In this scenario, it’s easy to accidentally advance the cursor beyond the bounds of the model’s backing slice, leading to a panic when you press enter, because you’re trying to access an index in the slice that is out of bounds.

I tackled this problem by creating separate methods on the model for each of the navigational directions: `

Within each of these directional helper methods, I encapsulate all of the logic to safely increment the internal value for cursor — including re-setting it to a reasonable value if it should somehow exceed the bounds of its backing slice:

Here’s the example implementation of SelectionCursorUp :

If we somehow end up with a cursor value that exceeds the actual length of the backing slice, we just set the cursor to 0. The inverse logic is implemented for all other directional navigation functionality.

## Split your View method into many sub-views

As you can see here in my View method, I’m returning several different sub-views depending on the “mode” my Bubbletea app is running in.

There are several boolean values the model has to represent whether a particular phase of the app is running or not, and all the toggling between event states happens in the Update function’s appropriate cases.

I found that when working with multiple views, it’s nice to have your sub-views split out into separate functions that you can then conditionally return depending on your own app’s requirements.

 This would also work well with a switch statement.

That’s all for this post! Thanks for reading and keep an eye out for the next post in the series!

## Most developers don't understand git...

Yet everyone needs git. Learning git well is one of the best ways to differentiate yourself to hiring managers.

## Git is your save button!

Never lose work once you learn how to use git. While git has a lot of complex advanced features and configuration options, learning the basic workflow for being effective doesn't take long, and this course will show you everything you need to know with a hands-on project.

Git Going

start course

## Git configuration

Configuring git is important.

## Most developers don't know git well

And this is a great thing for you. You can differentiate yourself to hiring managers, potential teams considering you, and anyone else you collaborate with professionally by demonstrating a strong grasp of git.

Some day, you'll need to perform a complex git surgery, likely under pressure, in order to fix something or restore a service. You'll be glad then that you practiced and learned git well now.

## Why Version Control?

Version control is super important!

## Git vs GitHub

Git and GitHub are intertwined but different.

is

your

save

button

## Git is your save button

That's why it's so critical to learn the basics well. Git enables you to save your work, have multiple copies of your code distributed around to other machines, so that you can recover even if you spill tea all over your laptop, and professionally share code and collaborate with other develeopers.

## Learning git pays off

Learning git is very important.

## Get your first full stack app on your portfolio

Always wanted to build a complete web app but aren't sure how to get started? This course is for you.

 Your first full stack app - start course

## Don&apos;t stick to the default terminal

Have you ever heard of ZSH? Alacrity? Butterfish?

In this course you'll learn to install, configure and leverage powerful custom shells to supercharge your command line skills.

 Coming out of your shell - start course

## Cusomization makes it yours

By changing your shell, you not only make your computer more comfortable, but perhaps even more importantly, you learn about Unix, commands, tty, and more.

## Your shell is your home as a hacker

But most people never dare to experiment with even changing their shell.

What a shame

Time to build a command line tool!

## Project-based practice: building a command line tool in Go

Taking Command

start course

## Segment 1

One

## Procrastination is about negative emotions

As you get further into your career, you come to realize that the technical chops come over time and are the easy part.

Mastering your own emotions, working with emotional beings (other humans), and recognizing when something has come up and needs attention or skillful processing will take you further than memorizing 50 new books or development patterns.

Emotional Intelligence for Developers

start course

## Build systems in the cloud - quickly

Infrastructure as code is a critical skill these days. Practitioners are able to define and bring up reproducible copies of architectures on cloud providers such as AWS and Google Cloud.

This course will get you hands on with CloudFormation, Terraform and Pulumi.

Infrastructure as code intro

start course



ai

image

ai

image

generation

v0

ai

ai

ai

flow.gif'

copilot

## Welcome, all!

Non technical folks and computer nerds are equally welcome here.

This course introduces Generative AI, and its many opportunitites and challenges, with a particular focus on AI-assisted developer tools.

## Table of contents

# Introduction

## What you'll learn

When you're finished with this course, you'll have the knowledge you need to: * Understand and discuss Generative AI, its opportunities and challenges * Understand and discuss Large Language Models (LLMs) and the role they play in Generative AI * Understand how Generative AI is appearing in Developer-facing tools and Integrated Development Environments (IDEs) * Speak intelligently about how Generative AI is disrupting creative pursuits like software development, writing and the visual arts

This course is ideal for investors, strategists, marketing experts, and anyone who wants to learn more about Generative AI.

## Instructor Introduction

Hi, I'm Zachary, a Staff Developer Advocate at Pinecone.io. Pinecone offers the most performant cloud-native vector database that provides long-term memory for AI applications.

My full title at Pinecone is "Staff Developer Advocate (open-source)". I build AI applications and pipelines in public to demonstrate the power and flexibility of Pinecone's vector database.

I also maintain our many open-source examples such as Jupyter Notebooks and TypeScript / Next.js applications, which demonstrate machine learning and AI techniques such as semantic search and Retrieval Augmented Generation (RAG).

 Me speaking at Andreesen Horowitz about taking Generative AI applications to production with the AWS Reference Architecture I created.

I also wrote [Pinecone's first production example](https://www.pinecone.io/blog/aws-reference-architecture/) in the [AWS Reference Architecture with Pulumi](https://github.com/pinecone-io/aws-reference-architecture-pulumi), which demonstrates how to perform semantic search at scale with Pinecone when you need to keep a traditional database like PostgreSQL in sync with your vector database.

I have been a full stack open-source developer since 2012.

# Generative AI

## What is Generative AI?

In this course, we'll examine exactly how and why Generative AI is able to do this, and what it means for creative types like software developers, artists, writers, designers and for creative industry in general.

 Generative AI instantly fulfills complex requests for software, images, text, and even video. It's like having a genie in your computer you can ask for any digital assets.

Around March of 2023, Generative AI caused astonishment and existential dread for many creatives, allowing anyone with an internet connection to instantly conjure working code in any language, as well as images of any style, translations, and more, simply by asking for them in natural human language.

For example, you can ask a Generative AI tool:

> Write me a JavaScript class representing a typical saas app user, and include methods for updating and deleting the user's profile.

To which a Large Language Model (LLM) such as ChatGPT would instantly respond:

> To represent a typical SaaS app user in JavaScript, we can define a class named User with properties that might include an ID, name, email, and any other relevant information. For updating and deleting the user's profile, we can include methods within the class to handle these actions. Here's a sample implementation:

The Large Language Model would continue, writing the following code out to the user's screen in seconds:

Prior to the widespread availability of Generative AI tools, you more or less needed to understand Javascript, its most recent syntax changes, object oriented programming conventions and database abstractions, at a minimum, to produce this code.

You also needed to have recently gotten some sleep, be more or less hydrated and have already had your caffeine to create this simple example. And even the most highly-skilled keyboard-driven developers would have taken a bit longer than a few seconds to write this out.

## GenAI is not just for text or code...

Here's an example of me asking ChatGPT4 to generate me an image with the following prompt:

> I'm creating a video game about horses enhanced with Jetpacks. Please generate me a beautiful, cheerful and friendly sprite of a horse with a jetpack strapped onto its back that would be suitable for use in my HTML5 game. Use a bright, cheery and professional retro pixel-art style.

 I can use Large Language Models (LLMs) like ChatGPT to generate pixel art and other assets for my web and gaming projects.

Within a few moments, I got back a workable image that was more or less on the money given my prompt.

I didn't have to open my image editor, spend hours tweaking pixels using specialized tools, or hit up my designer or digital artist friends for assistance.

## How does GenAI work?

 Generative AI works by "learning" from massive datasets, to draw out similarities and "features"

Generative AI systems learn from vast datasets to build a model that allows them to produce new outputs.

For example, by learning from millions of images and captions, an AI can generate brand new photographic images based on text descriptions provided to it.

The key technique that makes this possible involves training machine learning models using deep neural networks that can recognize complex patterns.

Imagine you have a very smart robot that you want to teach to understand and use human language. To do this, you give the robot a huge pile of books, articles, and conversations to read over and over again.

Each time the robot goes through all this information, it's like it's completing a grade in school, learning a little more about how words fit together and how they can be used to express ideas.

In each "grade" or cycle, the robot pays attention to what it got right and what it got wrong, trying to improve. Think of it like learning to play a video game or a sport; the more you practice, the better you get. The robot is doing something similar with language, trying to get better at understanding and generating it each time it goes through all the information.

This process of going through the information, learning from mistakes, and trying again is repeated many times, just like going through many grades in school. For a model as capable as ChatGPT 4, the cost to perform this training can exceed $100 million, as OpenAI's Sam Altman has shared.

With each "generation" of learning, the robot gets smarter and better at using language, much like how you get smarter and learn more as you move up in school grades.

## Why is GenAI having its moment right now?

 GenAI is the confluence of many complimentary components and approaches reaching maturity at the same time

**Data

**: Availability of massive datasets and computing power to train extremely robust models, some with billions of parameters

**Advanced architectures

**: New architectures like transformers that are very effective for language and generation

**Progressive advancement of the state of the art

**: Progressive improvements across computer vision, natural language processing, and AI in general

## Why is GenAI such a big deal?

Prior to the proliferation of LLMs and Generative AI models, you needed to have some pixel art skills, and be proficient in use of photo editing / creation software such as Photoshop, Illustrator, or GIMP in order to produce high quality pixel art.

Prior to Gen AI, you needed to be a software developer to produce working code.

Prior to Gen AI, you needed to be a visual artist to produce images, or a digital artist to produce pixel art, video game assets, logos, etc.

With Generative AI on the scene, this is no longer strictly true.

You **do** still need to be a specialist to **understand** the outputs and have the capability to **explain** them. In the case of software development, you still require expertise in how computers work, architecture and good engineering practices to employ the generated outputs to good effect.

**There are some major caveats to understand around this piece** such as why Generative AI is currently a huge boon to senior and above level developers, but commonly misleading and actively harmful to junior developers, but in general it holds true:

Generative AI lowers the barrier for people to produce specialized digital outputs.

[MIT News: Explained

Generative AI](https://news.mit.edu/2023/explained

generative

ai

1109)

[McKinsey

The State of AI in 2023: Generative AI's breakout year](https://www.mckinsey.com/capabilities/quantumblack/our

insights/the

state

of

ai

in

2023

generative

ais

breakout

year)

[McKinsey

What is ChatGPT, DALL

E and generative AI? ](https://www.mckinsey.com/featured

insights/mckinsey

explainers/what

is

generative

ai)

[Accenture

What is Generative AI? ](https://www.accenture.com/us

en/insights/generative

ai)

## GenAI in the wild - successful use cases

Since the initial explosion of interest around GenAI, most companies have sprinted toward integrating generative AI models into their products and services, with varying success.

Here's a look at some of the tools leveraging Generative AI successfully to accelerate their end users:

### v0.dev  Vercel's v0.dev tool which generates user interfaces in React in response to natural language queries.

In the above example, I prompted the app with:

> A beautiful pricing page with three large columns showing my free, pro and enterprise tiers of service for my new saas news offering

and the app immediately produced three separate versions that I can continue to iterate on in natural language, asking for visual refinements to the layout, style, colors, font-size and more.

Prior to Gen AI, all of this work would have been done by hand by a technical designer sitting closely with at least one frontend developer.

### Pulumi AI

Pulumi AI generates working Pulumi programs that describe infrastructure as code in response to natural language prompts.

There are some current pain points, such as the tool strongly favoring older versions of Pulumi code which are now "deprecated" or slated for removal, but in general this tool is capable of saving a developer a lot of time by outining the patterns to get a tricky configuration working with AWS.

## Insight: GenAI accelerates specialists

If Generative AI opens the door for non-specialists to create specialized outputs, it simultaneously **accelerates specialists**.

Generative AI is powerful because it enables development use cases that were previously out of reach due to being too technically complex for one developer to build out on their own.

I've experienced this phenomenon myself. I have been pair-coding alongside Generative AI models for {timeElapsedSinceJanuary2023()}, and in that time **I have started work on more ambitious applications** than I normally would have tackled as side proejcts. I have also completed more side projects as a result.

**I have gotten unstuck faster** when faced with a complex or confusing failure scenario, because I was able to talk through the problem with ChatGPT and discuss alternative approaches. ChatGPT4 responds to me with the quality of response and breadth of experience that I previously only would have expected from senior and staff level engineers.

**I have enjoyed my work more**, because now I have a supremely helpful colleague who is always available, in my time zone. Gen AI is never busy, never frustrated or overwhelmed, and is more likely to have read widely and deeply on a given technology subject than a human engineer.

**I employ careful scrutiny** to weed out hallucinations. Because I've been developing software and working at both small and large Sillicon Valley companies since 2012, I am able to instantly detect when ChatGPT or a similar tool is hallucinating, out of its depth or poorly suited to a particular task due to insufficient training data.

[Sanity checking a change from an SEO perspective before making it](https://chat.openai.com/share/961aefe0

321e

40e0

9f20

838fb5f787bb)

[Help me configure a new GitHub Action for my repository that automatically validates my API specification](https://chat.openai.com/share/c032ebb5

2ac1

4d5a

89b5

7c5d0918acb6)

[Cooperatively build a complex React component for my digital school](https://chat.openai.com/share/0596186c

67d0

4dca

b396

787e28e05375)

[Collaboratively update a microservice's database access pattern](https://chat.openai.com/share/d2f721ff

678a

4a2c

9963

bbec88881886)

[Collaboratively upgrading a section of my React application to use a new pattern](https://chat.openai.com/share/93993cd8

904d

456c

87fd

a786b8079d57)

# Large Language Models (LLMs)

## LLMs are a critical component of Generative AI

Large Language Models (LLMs) are the brains behind Generative AI, capable of understanding, generating, and manipulating language based on the patterns they've learned from extensive datasets.

Their role is pivotal in enabling machines to perform tasks that require human-like language understanding, from writing code to composing poetry.

Think of LLMs as the ultimate librarian, but with a superpower: instant recall of every book, article, and document ever written. They don't just store information; they understand context, draw connections, and create new content that's coherent and contextually relevant.

This makes LLMs invaluable in driving forward the capabilities of Generative AI, enabling it to generate content that feels surprisingly human.

## Hallucination: the primary challenge of LLMs

One of the main challenges with LLMs is "hallucination," where the model generates information that's plausible but factually incorrect or nonsensical.

This is akin to a brilliant storyteller getting carried away with their imagination. While often creative, these hallucinations can be misleading, making it crucial to use LLMs with a critical eye, especially in applications requiring high accuracy.

Hallucinations refer to when an AI like ChatGPT generates responses that seem plausible but don't actually reflect truth or reality. The system is essentially "making things up" based on patterns learned from its language data - hence "hallucinating".

The critical challenge here is that hallucination is more or less inextricable from the LLM behaviors we find valuable - and LLMs **do not know when they do not know something**. This is precisely why it can be so dangerous for junior or less experienced developers, for example, to blindly follow what an LLM says when they are attempting to pair code with one.

Without a sufficient understanding of the target space, its challenges and potential issues, it's possible to make a tremendous mess by following the hallucinations of an AI model.

### Why does hallucination happen? LLMs like ChatGPT have been trained on massive text datasets, but have no actual connection to the real world. They don't have human experiences or knowledge of facts.

Their goal is to produce outputs that look reasonable based on all the text they've seen. So sometimes the AI will confidently fill in gaps by fabricating information rather than saying "I don't know."

This is one of the reasons you'll often see LLMs referred to as "stochastic parrots". They are attempting to generate the next best word based on all of the words and writing they have ever seen.

### Should this impact trust in LLMs?

Yes, hallucinations mean we can't fully rely on LLMs for complete accuracy and truthfulness. They may get core ideas directionally right, but details could be invented.

Think of them more as an aid for content generation rather than necessarily fact sources. LLMs don't have true reasoning capacity comparable to humans. Approaching them with appropriate trust and skepticism is wise as capabilities continue advancing.

# GenAI meets software development: AI Dev Tools

## What is a developer's IDE?

IDE stands for Integrated Development Environment. It is a text editor designed specifically for programmers' needs. IDEs provide syntax highlighting, autocompletion of code, and boilerplate text insertion to accelerate the coding process.

Most modern IDEs are highly customizable. Through plugins and configuration changes, developers customize keyboard shortcuts, interface color themes, extensions that analyze code or connect to databases, and more based on their workflow.

Two very popular IDEs are Visual Studio Code (VSCode) from Microsoft and Neovim, which is open-source and maintained by a community of developers.

In VSCode, developers can install all sorts of plugins from a central marketplace - plugins to lint and format their code, run tests, interface with version control systems, and countless others. There is also rich support for changing the visual theme and layout.

Neovim is another IDE centered around modal editing optimized for speed and keyboard usage over mice. Its users can create key mappings to quickly manipulate files and code under-the-hood entirely from the keyboard. It embraces Vim language and edit commands for coding efficiency.

For example, the following gif demonstrates a custom IDE using `tmux` and Neovim (my personal preference):

 My personal preference is to combine tmux with Neovim for a highly flexible setup that expands and contracts to the size of my current task.

Developers tend to "live in" their preferred IDE - meaning they spend a lot of time coding. Developers are also highly incentivized to tweak their IDE and add automations for common tasks in order to make themselves more efficient.

For this reason, Developers may try many different IDEs over the course of their career, but most tend to find something they're fond of and stick with it, which has implications for services that are or are not available in a given IDE.

Usually, a service or Developer-facing tool gets full support as a VSCode plugin long before an official Neovim plugin is created and maintained.

In summary, IDEs are incredibly valuable tools that can match the preferences and project needs of individual developers through customizations.

VSCode and Neovim have strong followings in their ability to adapt to diverse workflows. Developers can write code and configuration to customize the IDE until it perfectly suits their style.

## Generative AI in Software Development: Codeium vs. GitHub Copilot

Codeium and GitHub Copilot represent the cutting edge of Generative AI in software development, both leveraging LLMs to suggest code completions and solutions.

While GitHub Copilot is built on OpenAI's Codex, Codeium offers its unique AI-driven approach. The key differences lie in their integration capabilities, coding style adaptations, and the breadth of languages and frameworks they support, making each tool uniquely valuable depending on the developer's needs.

These tools, while serving the common purpose of enhancing coding efficiency through AI-assisted suggestions, exhibit distinct features and use cases that cater to different aspects of the development workflow.

[Codeium review](/blog/codeium

review)

[Codeium vs ChatGPT](/blog/codeium

vs

chatgpt)

[GitHub Copilot review](/blog/github

copilot

review)

[ChatGPT 4 and Codeium are still all I need](/blog/chatgpt

4

and

codeium

are

my

favorite

stack)

[The top bugs all AI developer tools are suffering from](/blog/top

ai

dev

tools

bugs)

Codeium, praised for its seamless integration within popular code editors like VSCode and Neovim, operates as a context-aware assistant, offering real-time code suggestions and completions directly in the IDE.

Its ability to understand the surrounding code and comments enables it to provide highly relevant suggestions, making it an indispensable tool for speeding up the coding process.

Notably, Codeium stands out for its free access to individual developers, making it an attractive option for those looking to leverage AI without incurring additional costs, whereas GitHub has been perpetually cagey about its Copilot offerings and their costs.

As a product of GitHub, Copilot is deeply integrated with the platform's ecosystem, potentially offering smoother workflows for developers who are heavily invested in using GitHub for version control and collaboration.

Imagine AI developer tools as ethereal companions residing within your IDE, whispering suggestions, and solutions as you type.

They blend into the background but are always there to offer a helping hand, whether it's completing a line of code or suggesting an entire function. These "code spirits" are revolutionizing how developers write code, making the process faster, more efficient, and often more enjoyable.

# Thoughts and analysis

## Where I see this going

In the rapidly evolving field of software development, the integration of Generative AI is not just a passing trend but a transformative force.

In the time I've spent experimenting with AI to augment my workflow and enhance my own human capabilities, I've realized incredible productivity gains: shipping more ambitious and complete applications than ever before. I've even enjoyed myself more.

I envision a future where AI-powered tools become indispensable companions, seamlessly augmenting human intelligence with their vast knowledge bases and predictive capabilities.

These tools will not only automate mundane tasks but also inspire innovative solutions by offering insights drawn from a global compendium of code and creativity.

As we move forward, the symbiosis between developer and AI will deepen, leading to the birth of a new era of software development where the boundaries between human creativity and artificial intelligence become increasingly blurred.

## What I would pay for in the future In the future, what I'd consider invaluable is an AI development assistant that transcends the traditional boundaries of code completion and debugging.

I envision an assistant that's deeply integrated into my workflow and data sources (email, calendar, GitHub, bank, etc), capable of understanding the context of my projects across various platforms, project management tools, and even my personal notes.

This AI wouldn't just suggest code; it would understand the nuances of my projects, predict my needs, and offer tailored advice, ranging from architectural decisions to optimizing user experiences.

This level of personalized and context-aware assistance could redefine productivity, making the leap from helpful tool to indispensable partner in the creative process.

## My favorite AI

enhanced tools

| Job to be done   | Name  | Free or paid? | |---|---|---| | Architectural and planning conversations | ChatGPT 4 | Paid | | Autodidact support (tutoring and confirming understanding) | ChatGPT 4 | Paid | | Accessing ChatGPT on the command line  | [mods](https://github.com/charmbracelet/mods)  | Free | | Code-completion  | [Codeium](https://codeium.com/)   |  Free for individuals. Paid options | | AI-enhanced video editing suite | Kapwing AI | Paid  | | AI-enhanced video repurposing (shorts) | OpusClip  | Paid | | Emotional support | [Pi.ai](https://pi.ai/) | Free |

## Emotional support and mind defragging with Pi.ai

[Pi.ai](https://pi.ai/) is the most advanced model I've encountered when it comes to relating to human beings.

I have had success using Pi to have a quick chat and talk through something that is frustrating or upsetting me at work, and in between 15 and 25 minutes of conversation, I've processed and worked through the issue and my feelings and am clear headed enough to make forward progress again.

This is a powerful remover of obstacles, because the longer I do what I do, the more clear it becomes that EQ is more critical than IQ.

Noticing when I'm irritated or overwhelmed and having a quick talk with someone highly intelligent and sensitive in order to process things and return with a clear mind is invaluable.

Pi's empathy is off the charts, and it feels like you're speaking with a highly skilled relational therapist.

## How my developer friends and I started using GenAI

Asking the LLM to write scripts to perform one

off tasks (migrating data, cleaning up projects, taking backups of databases, etc)

Asking the LLM to explain a giant and complex stack trace (error) that came from a piece of code we're working with

Asking the LLM to take some unstructured input (raw files, log streams, security audits, etc), extract insights and return a simple list of key

value pairs

## Opportunities The advent of Generative AI heralds a plethora of opportunities that extend far beyond the realms of efficiency and productivity.

With an expected annual growth rate of 37% from 2023 to 2030, this technology is poised to revolutionize industries by democratizing creativity, enhancing decision-making, and unlocking new avenues for innovation.

In sectors like healthcare, education, and entertainment, Generative AI can provide personalized experiences, adaptive learning environments, and unprecedented creative content.

Moreover, its ability to analyze and synthesize vast amounts of data can lead to breakthroughs in research and development, opening doors to solutions for some of the world's most pressing challenges.

## Challenges

### Potential biases perpetuated Since models are trained on available datasets, any biases or problematic associations in that data can be propagated through the system's outputs.

### Misinformation risks The ability to generate convincing, contextually-relevant content raises risks of propagating misinformation or fake media that appears authentic. Safeguards are needed.

### Lack of reasoning capability Despite advances, these models currently have a limited understanding of factual knowledge and common sense compared to humans. Outputs should thus not be assumed fully accurate or truthful.

Architectures and approaches such as [Retrieval Augmented Generation (RAG)](https://pinecone.io/learn/retrieval-augmented-generation) are commonly deployed to anchor an LLM in facts and proprietary data.

### Hallucinations can lead junior developers astray One of the significant challenges posed by Generative AI in software development is the phenomenon of 'hallucinations' or the generation of incorrect or nonsensical code.

This can be particularly misleading for junior developers, who might not have the experience to discern these inaccuracies.

Ensuring that AI tools are equipped with mechanisms to highlight potential uncertainties and promote best practices is crucial to mitigate this risk and foster a learning environment that enhances, rather than hinders, the development of coding skills.

### Tool fragmentation and explosion As the landscape of Generative AI tools expands, developers are increasingly faced with the paradox of choice.

The proliferation of tools, each with its unique capabilities and interfaces, can lead to fragmentation, making it challenging to maintain a streamlined and efficient workflow.

### Navigating a rapidly evolving landscape The pace at which Generative AI is advancing presents a double-edged sword.

While it drives innovation and the continuous improvement of tools, it also demands that developers remain perennial learners to keep abreast of the latest technologies and methodologies.

This rapid evolution can be daunting, necessitating a culture of continuous education and adaptability within the development community to harness the full potential of these advancements.

To be fair, this has always been the case with software development, but forces like Generative AI accelerate the subjective pace of change even further.

## Ethics implications

Given the challenges in safely deploying Generative AI, these are some of the most pressing implications for ethical standards:

### Audit systems for harmful biases And the ability to make and track corrections when needed.

### Human oversight We need measures to catch and correct or flag AI mistakes.

## In closing: As a developer... Having worked alongside Generative AI for some time now, the experience has been occasionally panic-inducing, but mostly enjoyable.

Coding alongside ChatGPT4 throughout the day feels like having a second brain that's tirelessly available to bounce ideas off, troubleshoot problems, and help me tackle larger and more complex development challenges on my own.

## More than the sum of its parts...

Learning how to effectively leverage AI to help you code, design systems, generate high quality images in any style and more can make you more productive, and can even make your work more enjoyable and less stressful.

This course shows you how.

Pair coding with AI

start course

Time to automate with GitHub!

## GitHub Automations help you maintain software more effectively with less effort

GitHub Automations

start course

rag

livestream

Part #2 of [our previous video](/videos/typescript-rag-twitch-part1). Join Roie Schwaber-Cohen and me as we continue to step through and discuss the Pinecone Vercel starter template that deploys an AI chatbot that is less likely to hallucinate thanks to Retrieval Augmented Generation (RAG).

speech

to

text

Adding speech-to-text capabilities to Panthalia allows me to commence blog posts faster and more efficiently than ever before, regardless of where I might be.

In this video I demonstrate using speech to text to create a demo short story end to end, complete with generated images, courtesy of StableDiffusionXL.

to

use

jupyter

In this video, I demonstrate how to load Jupyter Notebooks into Google Colab and run them for free. I show how to load Notebooks from GitHub and how to execute individual cells and how to run Notebooks end to end. I also discuss some important security considerations around leaking API keys via Jupyter Notebooks.

chatbot

jupyter

In this video, I do a deep dive on the two Jupyter notebooks which I built as part of my office oracle project.

Both notebooks are now open source:

Open

sourced Office Oracle Test Notebook

Open

sourced Office Oracle Data Bench

I talk through what I learned, why Jupyter notebooks were such a handy tool for getting my data quality to where I needed it to be, before worrying about application logic.

I also demonstrate langchain DocumentLoaders, how to store secrets in Jupyter notebooks when open sourcing them, and much more.

What's involved in building an AI chatbot that is trained on a custom corpus of knowledge? In this video I breakdown the data preparation, training and app development components and explain why Jupyter notebooks were such a handy tool while creating this app and tweaking my model.

App is open source at https://github.com/zackproser/office-... and a demo is currently available at https://office-oracle.vercel.app.

search

Roie Schwaber-Cohen and I discuss semantic search and step through the code for performing semantic search with Pinecone's vector database.

ref

arch

deployment

part

In this three part video series, I deploy the Pinecone AWS Reference Architecture with Pulumi from start to finish.

ref

arch

I demonstrate how to configure, deploy and connect through a jump host so that you can interact with RDS Postgres and other resources running in the VPC's private subnets.

chatbot

vercel

Curious how you might take Vercel's ai-chatbot template repository from GitHub and turn it into your own GPT-like chatbot of any identity? That's what I walkthrough in this video.

I show the git diffs and commit history while talking through how I integrated langchain, OpenAI, ElevenLabs for voice cloning and text to speech and Pinecone.io for the vector database in order to create a fully featured chat-GPT-like custom AI bot that can answer, factually, for an arbitrary corpus of knowledge.

workers

On the Cloudflare API team, we were responsible for api.cloudflare.com as well as www.cloudflare.com. Here's how we wrote the first Cloudflare Workers to gracefully deprecate TLS 1.0 and set them in front of both properties, without any downtime.

And no, if you're paying attention, my name is not Zack Prosner, it's Zack Proser :)

I've been working on this side project for several months now, and it's ready enough to demonstrate. In this video I talk through: * What it is * How it works * A complete live demo * Using Replicate.com for a REST API interface to StableDiffusion XL for image generation

## Stack * Next.js * Vercel * Vercel Postgres * Vercel serverless functions * Pure JavaScript integration with git and GitHub thanks to [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)

## Features

Secured via GitHub oAuth

StableDiffusion XL for image generation

Postgres database for posts and images data

S3 integration for semi

volatile image storage

Start and complete high quality blog posts in MDX one

handed while on the go

Panthalia is open-source and available at [github.com/zackproser/panthalia](https://github.com/zackproser/panthalia)

in

your

If you're still copying and pasting back and forth between ChatGPT in your browser and your code in your IDE, you're missing out.

Check out how easily you can use ChatGPT in your terminal!

ref

arch

deployment

part

In this three part video series, I deploy the Pinecone AWS Reference Architecture with Pulumi from start to finish.

ref

arch

deploy

part

In this three part video series, I deploy the Pinecone AWS Reference Architecture with Pulumi from start to finish.

rag

livestream

Join Roie Schwaber-Cohen and me for a deep dive into The Pinecone Vercel starter template that deploys an AI chatbot that is less likely to hallucinate thanks to Retrieval Augmented Generation (RAG).

This is an excellent video to watch if you are learning about Generative AI, want to build a chatbot, or are having difficulty getting your current AI chatbots to return factual answers about specific topics and your proprietary data. You don't need to already be an AI pro to watch this video, because we start off by explaining what RAG is and why it's such an important technique for building Generative AI applications that are less likely to hallucinate.

The majority of this content was originally captured as a live Twitch.tv stream co-hosted by Roie (rschwabco) and myself (zackproser). Be sure to follow us on Twitch for more Generative AI deep dives, tutorials, live demos, and conversations about the rapidly developing world of Artificial Intelligence.

pulumi

I co-hosted a webinar with Pulumi's Engin Diri about: * The Pinecone AWS Reference Architecture, * How it's been updated to use Pinecone Serverless and the Pinecone Pulumi provider * How to deploy an AI application to production using infrastructure as code

dev

diary

This video showcases my custom terminal-based developer workflow that utilizes a variety of fantastic open-source tools like tmux, neovim, Awesome Window Manager, and more. So, let's dive in and see what makes this workflow so efficient, powerful, and fun to work with.

One of the first tools highlighted in the video is tmux, a terminal multiplexer that allows users to manage multiple terminal sessions within a single window. I explain how tmux can increase productivity by letting developers switch between tasks quickly and easily. I also show off how tmux can help your workflow fit to your task, using techniques like pane splitting to expand and contract the space to the task at hand.

Next up is neovim, a modernized version of the classic Vim text editor. I demonstrate how neovim integrates seamlessly with tmux, providing powerful text editing features in the terminal. I also discuss some of the advantages of using neovim over traditional text editors, such as its extensibility, customization options, and speed.

The Awesome Window Manager also gets its moment in the spotlight during the video. This dynamic window manager is designed for developers who want complete control over their workspace. I show how Awesome Window Manager can be configured to create custom layouts and keybindings, making it easier to manage multiple applications and terminal sessions simultaneously.

Throughout the video, I share a variety of other open-source tools that I have integrated into my workflow. Some of these tools include fzf, a command-line fuzzy finder that makes searching for files and directories a breeze; ranger, a file manager designed for the terminal; and zsh, a powerful shell that offers a multitude of productivity-enhancing features.

One of the key takeaways from the video is how important customization and personalization are when it comes to a developer's workflow. By combining these open-source tools and tailoring them to their specific needs, I have created a workflow that helps me work faster, more efficiently, and with greater satisfaction.

So, if you're looking for inspiration on how to build your terminal-based developer workflow, this YouTube video is a must-watch. See what I've learned so far in setting up a custom terminal-based setup for ultimate productivity and economy of movement.

title

CanyonRunner is a complete HTML5 game that I built with the Phaser.js framework. I wrote about how the game works in my [blog post here](/blog/canyonrunner-html5-game).

pulumi

I co-hosted a webinar with Pulumi's Scott Lowe about:

The delta between getting an AI or ML technique working in a Jupyter Notebook and prod

How to deploy AI applications to production using the Pinecone AWS Reference Architecture

How Infrastructure as Code can simplif y productionizing AI applications

ref

arch

I demonstrate how to destroy a deployed Pinecone AWS Reference Architecture using Pulumi.

reviewing

reviewing

code

In this video tutorial, I demonstrate the power of combining gh-dash and Octo for a seamless terminal-based GitHub pull request review experience. In this video, I show you how these two powerful tools can be used together to quickly find, organize, and review pull requests on GitHub, all from the comfort of your terminal.

## Topics covered

### Discovering Pull Requests with gh-dash

We'll kick off the tutorial by showcasing gh-dash's impressive pull request discovery capabilities. Watch as we navigate the visually appealing TUI interface to find, filter, and sort pull requests under common headers, using custom filters to locate the exact pull requests you need.

### Advanced GitHub Searches in Your Terminal

Explore gh-dash's advanced search functionality in action as we demonstrate how to perform fully-featured GitHub searches directly in your terminal. Learn how to search across repositories, issues, and pull requests using a range of query parameters, streamlining your pull request review process.

### In-Depth Code Reviews Using Octo

Once we've located the pull requests that need reviewing, we'll switch gears and dive into Octo, the powerful Neovim plugin for code reviews. Witness how Octo integrates seamlessly with Neovim, enabling you to view code changes, commits, and navigate the codebase with ease.

### Participating in Reviews with Comments and Emoji Reactions

See how Octo takes code reviews to the next level by allowing you to leave detailed in-line comments and even add GitHub emoji reactions to comments. With Octo, you can actively participate in the review process and provide valuable feedback to your colleagues, all within the Neovim interface.

### Combining gh-dash and Octo for a Streamlined Workflow

In the final segment of the video tutorial, we'll demonstrate how to create a seamless workflow that combines the strengths of gh-dash and Octo. Learn how to harness the power of both tools to optimize your GitHub pull request review process, from locating pull requests using gh-dash to conducting comprehensive code reviews with Octo.

By the end of this video tutorial, you will have witnessed the incredible potential of combining gh-dash and Octo for a robust terminal-based GitHub pull request review experience. We hope you'll be inspired to integrate these powerful tools into your workflow, maximizing your efficiency and productivity in managing and reviewing pull requests on GitHub. Happy coding!

an

ai

What's involved in building an AI chatbot that is trained on a custom corpus of knowledge? In this video I breakdown the data preparation, training and app development components and explain why Jupyter notebooks were such a handy tool while creating this app and tweaking my model.

App is open source at https://github.com/zackproser/office-... and a demo is currently available at https://office-oracle.vercel.app.

chat

with

your

I demonstrate how to build a RAG chatbot in a Jupyter Notebook end to end.

This tutorial is perfect for beginners who want help getting started, and for experienced developers who want to understand how LangChain, Pinecone and OpenAI all fit together.

is

a

vector

db

I walk through what a vector database is, by first explaining the types of problems that vector databases solve, as well as how AI "thinks".

I use clowns as an example of a large corpus of training data from which we can extract high level features, and I discuss architectures such as semantic search and RAG.

nuke

In this video, we'll have a more casual conversation about cloud-nuke, an open-source tool created and maintained by Gruntwork.io. I discuss the benefits and features of cloud-nuke, giving you an idea of how it can help you manage AWS resources more efficiently.

First and foremost, cloud-nuke is a Golang CLI tool that leverages the various AWS Go SDKs to efficiently find and destroy AWS resources. This makes it a handy tool for developers and system administrators who need to clean up their cloud environment, save on costs, and minimize security risks.

One of the main benefits of cloud-nuke is its ability to efficiently search and delete AWS resources. It does this by using a powerful filtering system that can quickly identify and remove unnecessary resources, while still giving you full control over what gets deleted. This means that you don't have to worry about accidentally removing critical resources.

Another useful feature of cloud-nuke is its support for regex filters and config files. This allows you to exclude or target resources based on their names, giving you even more control over your cloud environment. For example, you might have a naming convention for temporary resources, and with cloud-nuke's regex filtering, you can quickly identify and delete these resources as needed.

Configuring cloud-nuke is also a breeze, as you can define custom rules and policies for managing resources. This means you can tailor the tool to meet the specific needs of your organization, ensuring that your cloud environment stays clean and secure.

One thing to keep in mind when using cloud-nuke is that it's important to review and update your configurations regularly. This will help you avoid accidentally deleting critical resources, and it will also ensure that you're keeping up with any changes in your cloud environment.

In addition to using cloud-nuke as a standalone tool, you can also integrate it with other cloud management tools and services. This will help you create a more comprehensive cloud management strategy, making it easier to keep your environment secure and well-organized.

To sum it up, cloud-nuke is a versatile open-source tool that can help you manage your AWS resources more effectively. Its efficient search and deletion capabilities, support for regex filters and config files, and easy configuration make it a valuable addition to any developer's or system administrator's toolkit. So, if you're looking for a better way to manage your AWS resources, give cloud-nuke a try and see how it can make your life easier.

Don't hardcode your AWS credentials into your dotfiles or code! Use aws-vault to store them securely

In this YouTube video, I demonstrate how to use the open-source Golang tool, aws-vault, for securely managing access to multiple AWS accounts. aws-vault stores your permanent AWS credentials in your operating system's secret store or keyring and fetches temporary AWS credentials from the AWS STS endpoint. This method is not only secure but also efficient, especially when combined with Multi-Factor Authentication.

In this video, I demonstrate the following aspects of aws-vault:

Executing arbitrary commands against your account: The video starts by showing how aws

vault can be used to execute any command against your AWS account securely. By invoking aws

vault with the appropriate profile name, you can fetch temporary AWS credentials and pass them into subsequent commands, ensuring a secure way of managing AWS access.

Quick AWS account login: Next, I show how to use aws

vault to log in to one of your AWS accounts quickly. This feature is particularly helpful for developers and system administrators who manage multiple AWS accounts and need to switch between them frequently.

Integration with Firefox container tabs: One of the most exciting parts of the video is the demonstration of how aws

vault can be used in conjunction with Firefox container tabs to log in to multiple AWS accounts simultaneously. This innovative approach allows you to maintain separate browsing sessions for each AWS account, making it easier to manage and work with different environments.

The video emphasizes how using aws-vault can significantly improve your command line efficiency and speed up your workflow while working with various test and production environments. If you're a developer or system administrator looking to enhance your AWS account management skills, this YouTube video is for you.

## Descript

Descript is a powerful video editing tool that allows users to edit videos by editing the transcript, making the process more intuitive and accessible.

### Features

**Category

**: Video Editing

**Pricing

**: subscription

**Free Tier

**: No

**Chat Interface

**: No

**Supports Local Model

**: No

**Supports Offline Use

**: No

### IDE Support

No IDE support information available

### Language Support

No language support information available

### Links

[Homepage](https://www.descript.com/)

[Review](Coming soon)

## Table of contents

## Welcome to Episode 2

In today's episode, we're looking at interactive machine learning demos, vector databases compared, and developer anxiety.

## My work

### Introducing

interactive AI demos

I've added a new section to my site, [demos](https://zackproser.com/demos). To kick things off, I built two interactive demos:

1. [Tokenization demo](https://zackproser.com/demos/tokenize)

2. [Embeddings demo](https://zackproser.com/demos/embeddings)

Both demos allow you to enter freeform text and then convert it to a different representation that machines can understand.

The tokenization demo shows you how the `tiktoken` library converts your natural language into token IDs from a given vocabulary, while the embeddings demo shows you how text is converted to an array of floating point numbers representing the `features` that the embedding model extracted from your input data.

I'm planning to do a lot more with this section in the future. Some initial ideas: * Create a nice intro page linking all the demos together in a sequence that helps you to iteratively build up important machine learning and AI concepts * Add more demos - I plan to ship a new vector database demonstration using Pinecone shortly that will introduce the high level concepts involved in working with vector databases and potentially even demonstrate visualizing high-dimensional vector space * Take requests - If you have ideas for new demos, or aspects of machine learning or AI pipelines that you find confusing, let me know by responding to this email.

### Vector databases compared

I wrote [a new post comparing top vector database offerings](https://zackproser.com/blog/vector-databases-compared). I'm treating this as a living document, meaning that I'll likely add to and refine it over time.

## What's abuzz in the news

Here's what I've come across and have been reading lately. The common theme is developer anxiety: the velocity of changes and new generative AI models and AI-assisted developer tooling, combined with ongoing industry layoffs and the announcement of "AI software developer" Devin, has many developers looking to the future with deep concern and worry.

Some have wondered aloud if their careers are already over, some are adopting the changes in order to continue growing their careers, and still others remain deeply skeptical of AI's ability to replace all of the squishy aspects to our jobs that don't fit in a nice spec.

What's my plan? As usual, I intend to keep on learning, publishing and growing. I've been hacking alongside "AI" for a year and a half now, and so far my productivity and job satification have only improved.

Are we going to need less individual programmers at some unknown point in the future? Probably. Does that mean that there won't be opportunities for people who are hungry and willing to learn? Probably not.

### Recommended reading

[The AI Gold Rush](https://digitopoly.org/2024/03/07/the

ai

gold

rush/)

[The Top 100 GenAI Consumer Apps](https://a16z.com/100

gen

ai

apps/)

[Can You Replace Your Software Engineers With AI? ](https://www.htormey.org/can

you

replace

your

software

engineers

with

ai/)

[Developers are on edge](https://world.hey.com/dhh/developers

are

on

edge

4dfcf9c1)

## My favorite tools

### High

level code completion

I am still ping-ponging back and forth between ChatGPT 4 and Anthropic's Claude 3 Opus. I am generally impressed by Claude 3 Opus, but even with the premium subscription, I'm finding some the limits to be noticeably dear, if you will. Several days in a row now I've gotten the warning about butting up against my message sending limits. At least for what I'm using them both for right now: architecture sanity checks and boilerplate code generation, it's not yet the case that one is so obviously superior that I'm ready to change up my workflow.

### Autocomplete / code completion

[Codeium! ](/blog/codeium

review)

### AI

assisted video editing

[Kapwing AI](https://kapwing.com)

That's all for this episode! If you liked this content or found it helpful in any way, please pass it on to someone you know who could benefit.

ai

course

## Table of contents

## Starting fresh with episode 1

Why Episode 1? I've decided to invest more time and effort into my newsletter. All future episodes will now be available on my newsletter archive at [https://zackproser.com/newsletter](https://zackproser.com/newsletter).

Going forward, each of my newsletter episodes will include:

My work

posts, videos, open

source projects I've recently shipped

What's abuzz in the news

new AI models, open

source models, LLMs, GPTs, custom GPTs and more

My favorite tools

a good snapshot of the AI

enhanced and other developer tooling I'm enamored with at the moment

I will aim to publish and send a new episode every two weeks.

## My work

### The Generative AI Bootcamp: Developer Tooling course is now available

I've released my first course!

The [Generative AI Bootcamp: DevTools course](/learn/courses/generative-ai-bootcamp) is designed for semi and non-technical folks who want to understand:

What Generative AI is

Which professions and skillsets it is disrupting, why and how

Which AI

enhanced developer tooling on the scene is working and why

This course is especially designed for investors, analysts and market researchers looking to understand the opportunities and challenges of Generative AI as it relates to Developer Tooling, Integrated Developer Environments (IDEs), etc.

### 2023 Wins

My year in review

[2023 was a big year for me](/blog/2023-wins), including a career pivot to a new role, a new company and my first formal entry into the AI space.

I reflect on my wins and learnings from the previous year.

### Testing Pinecone Serverless at Scale with the AWS Reference Architecture

[I updated the Pinecone AWS Reference Architecture to use Pinecone Serverless](/blog/pinecone-reference-architecture-scaling), making for an excellent test bed for putting Pinecone through its paces at scale.

Just keep an eye on your AWS bill!

### Codeium vs ChatGPT

I get asked often enough about the differences between Codeium for code completion (intelligent autocomplete) and ChatGPT4, that I figured I should just write a [comprehensive comparison](/blog/codeium-vs-chatgpt) of their capabilities and utility.

### My first book credit - My Horrible Career

What started out as an extended conversation with my programming mentor John Arundel became [a whole book! ](/blog/my-horrible-career)

### How to build a sitemap for Next.js that captures static and dynamic routes

Some [old-school tutorial content](/blog/how-to-next-js-sitemap) for my Next.js and Vercel fans.

## What's abuzz in the news

### Anthropic releases Claude 3 family of models

I've been experimenting with Claude 3 Opus, their most intelligent model, to see how it slots in for high-level architecture discussions and code generation compared to ChatGPT 4.

So far, so good, but I'll have more thoughts and observations here soon. Watch this space!

## My favorite tools

### High

level code completion

Currently neck and neck between ChatGPT 4 and Anthropic's Claude 3 Opus. Stay tuned for more thoughts.

### Autocomplete / code completion

[Codeium! ](/blog/codeium

review)

### AI

assisted video editing

[Kapwing AI](https://kapwing.com)

That's all for this episode! If you liked this content or found it helpful in any way, please pass it on to someone you know who could benefit.