DEV Community

Cover image for How Hermes Agent Helped Me Ship an Indonesian NLP Parser in One Week
semi
semi

Posted on

How Hermes Agent Helped Me Ship an Indonesian NLP Parser in One Week

Hermes Agent Challenge Submission: Build With Hermes Agent

This is a submission for the Hermes Agent Challenge: Build With Hermes Agent

What I Built

Warung MiMo is an AI-powered assistant for Indonesian warungs, small neighborhood shops that run a huge part of daily commerce in Indonesia. The assistant lets shop owners manage inventory, track debts, and log sales using natural Indonesian. Voice input, text input, receipt scanning. Whatever feels natural to them.

The core challenge was language.

Indonesian warung owners do not speak in clean, structured commands. They say things like:

  • "Bu Sari utang empat puluh dua ribu" (Mrs. Sari owes forty-two thousand)
  • "Indomie tinggal lima bungkus" (Indomie left, five packs)
  • "Aqua habis, gula sisa setengah kg" (Aqua out of stock, sugar half a kg left)

That one sentence contains multiple actions, numbers written as Indonesian words, product names in shorthand, and a debt instruction. Generic NLP cannot handle this. I needed a custom parser built from scratch.

And I needed Hermes Agent to make it happen.

Demo

Live: warung-mimo.vercel.app
Source: github.com/iyop666/warung-mimo

Try these inputs in the assistant page:

  • "Indomie tinggal lima bungkus"
  • "Aqua habis, gula sisa setengah kg"
  • "Bu Sari utang empat puluh dua ribu"
  • "bayar utang Bu Sari"
  • "Catat utang Pak Budi 50rb, terus telur tinggal tiga"
  • "Teh Botol kosong, kopi sisa dua belas sachet"

The assistant parses each sentence, identifies products, extracts numbers (even written as Indonesian words), and generates structured actions like stock updates or debt records.

Code

The full source is at github.com/iyop666/warung-mimo.

Here are the key modules that Hermes Agent helped build:

Indonesian Number Parser

This was the hardest part. Indonesian compound numbers work differently from English. "Empat puluh dua" means forty-two (4 x 10 + 2). The parser needed to handle the "puluh" (tens) multiplier logic.

const NUMBER_WORDS: Record<string, number> = {
  nol: 0, satu: 1, se: 1, dua: 2, tiga: 3, empat: 4,
  lima: 5, enam: 6, tujuh: 7, delapan: 8, sembilan: 9,
  sepuluh: 10, sebelas: 11,
  "dua belas": 12, "tiga belas": 13, "empat belas": 14,
  "lima belas": 15, "enam belas": 16, "tujuh belas": 17,
  "delapan belas": 18, "sembilan belas": 19,
  "dua puluh": 20, "tiga puluh": 30, "empat puluh": 40,
  setengah: 0.5, seperempat: 0.25,
  "1/2": 0.5, "1/4": 0.25, "3/4": 0.75,
};

function parseIndonesianNumber(text: string): number | null {
  const lower = text.toLowerCase().trim();
  const sortedKeys = Object.keys(NUMBER_WORDS).sort((a, b) => b.length - a.length);

  for (const key of sortedKeys) {
    if (lower === key) return NUMBER_WORDS[key];
  }

  let compound = 0;
  let matched = false;
  const parts = lower.split(/\s+/);
  for (const part of parts) {
    if (NUMBER_WORDS[part] !== undefined) {
      if (part === "puluh") continue;
      const idx = parts.indexOf(part);
      if (idx < parts.length - 1 && parts[idx + 1] === "puluh") {
        compound += NUMBER_WORDS[part] * 10;
        matched = true;
      } else if (idx > 0 && parts[idx - 1] === "puluh") {
        compound += NUMBER_WORDS[part];
        matched = true;
      } else {
        compound += NUMBER_WORDS[part];
        matched = true;
      }
    }
  }
  if (matched && compound > 0) return compound;

  const digitMatch = lower.match(/^(\d+(?:[.,]\d+)?)$/);
  if (digitMatch) return parseFloat(digitMatch[1].replace(",", "."));
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Product Matching System

Warung owners rarely use canonical product names. They say "Aqua" instead of "Aqua 600ml", "gula" instead of "Gula Pasir", "rokok" instead of "Rokok Sampoerna Mild". The matcher needed 30+ aliases for 8 products.

const PRODUCT_MAPPINGS: ProductMapping[] = [
  { keywords: ["indomie", "mie goreng", "mi goreng", "mie instan"], canonical: "Indomie Goreng", unit: "bungkus" },
  { keywords: ["aqua", "air mineral", "air botol"], canonical: "Aqua 600ml", unit: "botol" },
  { keywords: ["gula", "gula pasir"], canonical: "Gula Pasir", unit: "kg" },
  { keywords: ["telur", "telor", "telur ayam"], canonical: "Telur", unit: "kg" },
  { keywords: ["minyak", "minyak goreng", "minyak 1l"], canonical: "Minyak Goreng 1L", unit: "liter" },
  { keywords: ["kopi", "kopi kapal api", "kapal api"], canonical: "Kopi Kapal Api", unit: "sachet" },
  { keywords: ["rokok", "sampoerna", "sampoerna mild"], canonical: "Rokok Sampoerna Mild", unit: "bungkus" },
  { keywords: ["teh botol", "sosro", "teh sosro"], canonical: "Teh Botol Sosro", unit: "botol" },
];
Enter fullscreen mode Exit fullscreen mode

Debt Tracking Patterns

Four patterns for recording debt, four for settling. Each handles a different way Indonesians talk about money.

const debtPatterns = [
  // "bu sari utang 25 ribu"
  /(.+?)\s+(?:utang|hutang|ngutang)\s+(\d+(?:[.,]\d+)?)\s*(ribu|rb|jt|juta)?/i,
  // "catat utang bu sari 25000"
  /(?:catat|tulis|tambah)\s+(?:utang|hutang)\s+(.+?)\s+(\d+(?:[.,]\d+)?)/i,
  // "pak budi ngutang 15 ribu"
  /(\w+(?:\s+\w+)?)\s+ngutang\s+(\d+(?:[.,]\d+)?)\s*(ribu|rb|jt|juta)?/i,
  // "utang bu sari 50rb"
  /(?:utang|hutang)\s+(.+?)\s+(\d+(?:[.,]\d+)?)\s*(ribu|rb|jt|juta)?/i,
];

const settlePatterns = [
  /bayar\s+(?:utang|hutang)\s+(.+)/i,
  /(.+?)\s+(?:bayar|lunasi|lunas)\s+(?:utang|hutang)/i,
  /(.+?)\s+(?:sudah|udah)\s+bayar/i,
  /(?:utang|hutang)\s+(.+?)\s+(?:lunas|lunasi|sudah\s*dibayar)/i,
];
Enter fullscreen mode Exit fullscreen mode

Multi-Action Splitter

One sentence can contain a sale, a stock update, and a debt note. The parser splits by commas, "dan", "terus", "lalu", "juga".

const segments = lower.split(/[,;]|\bdan\b|\bterus\b|\blalu\b|\bjuga\b/);

for (const segment of segments) {
  const trimmed = segment.trim();
  if (trimmed.length < 3) continue;

  const product = matchProduct(trimmed);
  if (!product) continue;

  const remaining = parseRemainingFromContext(trimmed);
  if (remaining !== null) {
    actions.push({ type: "update_stock", item: product.name, remaining, unit: product.unit });
  }
}
Enter fullscreen mode Exit fullscreen mode

My Tech Stack

  • Frontend: Next.js 16, React 19, shadcn/ui, Tailwind 4
  • Language: TypeScript
  • Deployment: Vercel
  • AI Assistant: Hermes Agent (for development, deployment, and infrastructure management)
  • Platform: Telegram (as the interface to Hermes Agent)

How I Used Hermes Agent

Hermes Agent was not just a coding assistant. It was the operational backbone of the entire project. Here is exactly how it powered the build.

1. Code Generation and Debugging

When I needed the debt tracking regex patterns, I did not write them from scratch. I told Hermes:

"I need regex patterns for Indonesian debt recording. Handle variations like 'bu sari utang 25 ribu', 'catat utang bu sari 25000', 'pak budi ngutang 15 ribu'."

Hermes generated four patterns covering different Indonesian phrasings. I reviewed them, adjusted one edge case, and shipped. What would have taken 30 minutes of regex debugging took 5 minutes.

The same happened with the number parser. The compound logic for "empat puluh dua" (42) was tricky. I described the problem to Hermes, it suggested the "puluh" multiplier approach, and I implemented it.

2. One-Command Deployment

Every deployment followed the same pattern:

"Deploy Warung MiMo to Vercel."

Hermes would:

  1. Run the build
  2. Catch TypeScript errors
  3. Fix them automatically
  4. Rebuild
  5. Deploy
  6. Verify the URL was live
  7. Report back with the link

No terminal switching. No build log reading. No Vercel dashboard. One Telegram message, one response with the live URL.

3. Automated Testing

When I added new product aliases or changed regex patterns, I asked Hermes to test them:

"Test these inputs against the parser: 'empat puluh dua ribu', 'setengah dus', '42rb', '1/2'."

Hermes ran the parser against each input, reported which ones passed and which failed, and suggested fixes for the failures. This feedback loop was faster than writing unit tests for every edge case.

4. Documentation and Publishing

After building the core features, I needed to write about it. Hermes pulled code directly from the project repository, structured it into article format, and published drafts to Dev.to via API.

The article for the GitHub Finish-Up-A-Thon Challenge was written this way. Hermes extracted real code snippets from the codebase, organized them into the submission template, and published. I reviewed and edited, but the heavy lifting was automated.

5. Infrastructure Management

The project runs on a VPS for backend operations. Hermes manages the entire infrastructure:

  • SSH access with persistent memory of credentials
  • Automated backups via cron jobs
  • Health monitoring with Telegram alerts
  • Log rotation and disk cleanup

All of this happens through conversational commands. No manual SSH sessions. No remembering which port or which directory.

What Made Hermes Agent The Right Fit

The key was context persistence.

Hermes remembered:

  • The project structure and file locations
  • The TypeScript configuration and build process
  • The Vercel deployment setup and environment variables
  • The Indonesian language patterns we were building for
  • Previous debugging sessions and solutions

That meant every conversation started where the last one ended. No re-explaining. No context rebuilding. Just continuous development.

For a solo developer building a domain-specific project like Indonesian NLP, that continuity was the difference between shipping and abandoning.


Project Stats:

  • ~2,500 lines of TypeScript
  • 15 React components
  • 30+ Indonesian number word mappings
  • 50+ regex patterns for NLP parsing
  • 8 products with 30+ aliases
  • 8 debt/ settle regex patterns

Links:

Top comments (0)