{"slug": "how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week", "title": "How Hermes Agent Helped Me Ship an Indonesian NLP Parser in One Week", "summary": "A developer built a custom Indonesian NLP parser in one week using Hermes Agent, powering an AI assistant for warung (small shop) owners. The parser handles informal Indonesian commands containing multiple actions, numbers written as words, and product shorthand, extracting structured stock and debt updates from natural speech. The system, called Warung MiMo, processes voice and text input to let owners manage inventory and sales without structured commands.", "body_md": "*This is a submission for the Hermes Agent Challenge: Build With Hermes Agent*\n\nWarung 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.\n\nThe core challenge was language.\n\nIndonesian warung owners do not speak in clean, structured commands. They say things like:\n\nThat 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.\n\nAnd I needed Hermes Agent to make it happen.\n\n**Live:** [warung-mimo.vercel.app](https://warung-mimo.vercel.app)\n\n**Source:** [github.com/iyop666/warung-mimo](https://github.com/iyop666/warung-mimo)\n\nTry these inputs in the assistant page:\n\nThe assistant parses each sentence, identifies products, extracts numbers (even written as Indonesian words), and generates structured actions like stock updates or debt records.\n\nThe full source is at [github.com/iyop666/warung-mimo](https://github.com/iyop666/warung-mimo).\n\nHere are the key modules that Hermes Agent helped build:\n\nThis 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.\n\n``` js\nconst NUMBER_WORDS: Record<string, number> = {\n  nol: 0, satu: 1, se: 1, dua: 2, tiga: 3, empat: 4,\n  lima: 5, enam: 6, tujuh: 7, delapan: 8, sembilan: 9,\n  sepuluh: 10, sebelas: 11,\n  \"dua belas\": 12, \"tiga belas\": 13, \"empat belas\": 14,\n  \"lima belas\": 15, \"enam belas\": 16, \"tujuh belas\": 17,\n  \"delapan belas\": 18, \"sembilan belas\": 19,\n  \"dua puluh\": 20, \"tiga puluh\": 30, \"empat puluh\": 40,\n  setengah: 0.5, seperempat: 0.25,\n  \"1/2\": 0.5, \"1/4\": 0.25, \"3/4\": 0.75,\n};\n\nfunction parseIndonesianNumber(text: string): number | null {\n  const lower = text.toLowerCase().trim();\n  const sortedKeys = Object.keys(NUMBER_WORDS).sort((a, b) => b.length - a.length);\n\n  for (const key of sortedKeys) {\n    if (lower === key) return NUMBER_WORDS[key];\n  }\n\n  let compound = 0;\n  let matched = false;\n  const parts = lower.split(/\\s+/);\n  for (const part of parts) {\n    if (NUMBER_WORDS[part] !== undefined) {\n      if (part === \"puluh\") continue;\n      const idx = parts.indexOf(part);\n      if (idx < parts.length - 1 && parts[idx + 1] === \"puluh\") {\n        compound += NUMBER_WORDS[part] * 10;\n        matched = true;\n      } else if (idx > 0 && parts[idx - 1] === \"puluh\") {\n        compound += NUMBER_WORDS[part];\n        matched = true;\n      } else {\n        compound += NUMBER_WORDS[part];\n        matched = true;\n      }\n    }\n  }\n  if (matched && compound > 0) return compound;\n\n  const digitMatch = lower.match(/^(\\d+(?:[.,]\\d+)?)$/);\n  if (digitMatch) return parseFloat(digitMatch[1].replace(\",\", \".\"));\n  return null;\n}\n```\n\nWarung 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.\n\n``` js\nconst PRODUCT_MAPPINGS: ProductMapping[] = [\n  { keywords: [\"indomie\", \"mie goreng\", \"mi goreng\", \"mie instan\"], canonical: \"Indomie Goreng\", unit: \"bungkus\" },\n  { keywords: [\"aqua\", \"air mineral\", \"air botol\"], canonical: \"Aqua 600ml\", unit: \"botol\" },\n  { keywords: [\"gula\", \"gula pasir\"], canonical: \"Gula Pasir\", unit: \"kg\" },\n  { keywords: [\"telur\", \"telor\", \"telur ayam\"], canonical: \"Telur\", unit: \"kg\" },\n  { keywords: [\"minyak\", \"minyak goreng\", \"minyak 1l\"], canonical: \"Minyak Goreng 1L\", unit: \"liter\" },\n  { keywords: [\"kopi\", \"kopi kapal api\", \"kapal api\"], canonical: \"Kopi Kapal Api\", unit: \"sachet\" },\n  { keywords: [\"rokok\", \"sampoerna\", \"sampoerna mild\"], canonical: \"Rokok Sampoerna Mild\", unit: \"bungkus\" },\n  { keywords: [\"teh botol\", \"sosro\", \"teh sosro\"], canonical: \"Teh Botol Sosro\", unit: \"botol\" },\n];\n```\n\nFour patterns for recording debt, four for settling. Each handles a different way Indonesians talk about money.\n\n``` js\nconst debtPatterns = [\n  // \"bu sari utang 25 ribu\"\n  /(.+?)\\s+(?:utang|hutang|ngutang)\\s+(\\d+(?:[.,]\\d+)?)\\s*(ribu|rb|jt|juta)?/i,\n  // \"catat utang bu sari 25000\"\n  /(?:catat|tulis|tambah)\\s+(?:utang|hutang)\\s+(.+?)\\s+(\\d+(?:[.,]\\d+)?)/i,\n  // \"pak budi ngutang 15 ribu\"\n  /(\\w+(?:\\s+\\w+)?)\\s+ngutang\\s+(\\d+(?:[.,]\\d+)?)\\s*(ribu|rb|jt|juta)?/i,\n  // \"utang bu sari 50rb\"\n  /(?:utang|hutang)\\s+(.+?)\\s+(\\d+(?:[.,]\\d+)?)\\s*(ribu|rb|jt|juta)?/i,\n];\n\nconst settlePatterns = [\n  /bayar\\s+(?:utang|hutang)\\s+(.+)/i,\n  /(.+?)\\s+(?:bayar|lunasi|lunas)\\s+(?:utang|hutang)/i,\n  /(.+?)\\s+(?:sudah|udah)\\s+bayar/i,\n  /(?:utang|hutang)\\s+(.+?)\\s+(?:lunas|lunasi|sudah\\s*dibayar)/i,\n];\n```\n\nOne sentence can contain a sale, a stock update, and a debt note. The parser splits by commas, \"dan\", \"terus\", \"lalu\", \"juga\".\n\n``` js\nconst segments = lower.split(/[,;]|\\bdan\\b|\\bterus\\b|\\blalu\\b|\\bjuga\\b/);\n\nfor (const segment of segments) {\n  const trimmed = segment.trim();\n  if (trimmed.length < 3) continue;\n\n  const product = matchProduct(trimmed);\n  if (!product) continue;\n\n  const remaining = parseRemainingFromContext(trimmed);\n  if (remaining !== null) {\n    actions.push({ type: \"update_stock\", item: product.name, remaining, unit: product.unit });\n  }\n}\n```\n\nHermes Agent was not just a coding assistant. It was the operational backbone of the entire project. Here is exactly how it powered the build.\n\nWhen I needed the debt tracking regex patterns, I did not write them from scratch. I told Hermes:\n\n\"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'.\"\n\nHermes 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.\n\nThe 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.\n\nEvery deployment followed the same pattern:\n\n\"Deploy Warung MiMo to Vercel.\"\n\nHermes would:\n\nNo terminal switching. No build log reading. No Vercel dashboard. One Telegram message, one response with the live URL.\n\nWhen I added new product aliases or changed regex patterns, I asked Hermes to test them:\n\n\"Test these inputs against the parser: 'empat puluh dua ribu', 'setengah dus', '42rb', '1/2'.\"\n\nHermes 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.\n\nAfter 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.\n\nThe 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.\n\nThe project runs on a VPS for backend operations. Hermes manages the entire infrastructure:\n\nAll of this happens through conversational commands. No manual SSH sessions. No remembering which port or which directory.\n\nThe key was **context persistence**.\n\nHermes remembered:\n\nThat meant every conversation started where the last one ended. No re-explaining. No context rebuilding. Just continuous development.\n\nFor a solo developer building a domain-specific project like Indonesian NLP, that continuity was the difference between shipping and abandoning.\n\n**Project Stats:**\n\n**Links:**", "url": "https://wpnews.pro/news/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week", "canonical_source": "https://dev.to/iyop666/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week-2gpo", "published_at": "2026-05-27 20:18:04+00:00", "updated_at": "2026-05-27 20:41:17.012164+00:00", "lang": "en", "topics": ["natural-language-processing", "artificial-intelligence", "ai-agents", "ai-products", "ai-tools"], "entities": ["Hermes Agent", "Warung MiMo", "Indonesia"], "alternates": {"html": "https://wpnews.pro/news/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week", "markdown": "https://wpnews.pro/news/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week.md", "text": "https://wpnews.pro/news/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week.txt", "jsonld": "https://wpnews.pro/news/how-hermes-agent-helped-me-ship-an-indonesian-nlp-parser-in-one-week.jsonld"}}