{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Nishikanta Ray",
  "title_listed": "Your Computer Is Busy When You're Not: Building a Desktop Observability Tool with Electron",
  "keywords": "",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/nishikantaray/your-computer-is-busy-when-youre-not-building-a-desktop-observability-tool-with-electron-1m6k",
  "publish_date": "2026-08-23",
  "corpus_stage": "",
  "notes": "",
  "id": "64901f7bc16c",
  "collected_at": "2026-10-02T14:04:41",
  "title": "Your Computer Is Busy When You're Not: Building a Desktop Observability Tool with Electron - DEV Community",
  "body_markdown": "You close your laptop.\nThe screen goes dark.\nYou assume everything stopped.\nBut what if it didn't?\nMaybe Docker is still transferring data.\nMaybe Chrome is still downloading something.\nMaybe an application is consuming CPU in the background.\nMaybe your computer woke up at 3 AM, performed some background work, and went back to sleep.\nMost operating systems already expose pieces of this information.\nThe interesting part is putting those pieces together.\nThat's what I wanted to explore:\nCan we build an observability layer for a desktop computer using Electron?\nNot another CPU/RAM widget.\nSomething that answers:\nWhat is my computer actually doing?\nThe Idea\nImagine opening your desktop monitoring application and seeing:\nTODAY\n────────────────────────────────────\n\nComputer active        7h 42m\nIdle                   1h 13m\nSleep                  8h 05m\n\nNetwork\n↓ Download             6.8 GB\n↑ Upload               1.2 GB\n\nTop applications\n────────────────────────────────────\n\nVS Code                3h 42m\nChrome                 2h 18m\nDocker                 1h 31m\nSlack                  52m\n\nNetwork-heavy apps\n────────────────────────────────────\n\nDocker                 2.4 GB\nChrome                 1.8 GB\nVS Code                920 MB\nEnter fullscreen mode\nExit fullscreen mode\nNow imagine clicking\n\"What happened while I was away?\"\n18:42  User became idle\n18:51  Screen locked\n18:53  Docker network activity\n19:07  Background sync completed\n19:14  System entered sleep\nEnter fullscreen mode\nExit fullscreen mode\nThat's much more interesting than a CPU percentage.\nThe npm Package That Makes This Easier\nInstead of writing native system integrations for everything ourselves, we can start with:\nnpm\ninstall\nsysteminformation\nEnter fullscreen mode\nExit fullscreen mode\nsysteminformation\nprovides APIs for system information, CPU, memory, battery, filesystem, network, processes, Docker and more. It also supports Linux, macOS and Windows, although individual APIs have platform-specific support.\nFor example:\nconst\nsi\n=\nrequire\n(\n\"\nsysteminformation\n\"\n);\nasync\nfunction\ngetSystemStats\n()\n{\nconst\n[\ncpu\n,\nmemory\n,\nbattery\n,\nnetwork\n]\n=\nawait\nPromise\n.\nall\n([\nsi\n.\ncurrentLoad\n(),\nsi\n.\nmem\n(),\nsi\n.\nbattery\n(),\nsi\n.\nnetworkStats\n()\n]);\nreturn\n{\ncpu\n:\ncpu\n.\ncurrentLoad\n,\nmemory\n:\nmemory\n.\nactive\n,\nbattery\n:\nbattery\n.\npercent\n,\nnetwork\n};\n}\nconsole\n.\nlog\n(\nawait\ngetSystemStats\n());\nEnter fullscreen mode\nExit fullscreen mode\nNow our Electron main process can periodically collect system information.\nMonitoring Network Activity\nOne of the most useful APIs is:\nsi\n.\nnetworkStats\n()\nEnter fullscreen mode\nExit fullscreen mode\nIt provides network statistics including received/transmitted bytes and calculated per-second rates. The package calculates rates based on differences between successive calls, so the first call doesn't provide a meaningful rate yet.\nA simple monitor:\nconst\nsi\n=\nrequire\n(\n\"\nsysteminformation\n\"\n);\nsetInterval\n(\nasync\n()\n=>\n{\nconst\nstats\n=\nawait\nsi\n.\nnetworkStats\n();\nfor\n(\nconst\nnetwork\nof\nstats\n)\n{\nconsole\n.\nlog\n({\ninterface\n:\nnetwork\n.\niface\n,\ndownload\n:\nnetwork\n.\nrx_sec\n,\nupload\n:\nnetwork\n.\ntx_sec\n});\n}\n},\n1000\n);\nEnter fullscreen mode\nExit fullscreen mode\nNow we have something like:\nWi-Fi\nDownload: 2.4 MB/s\nUpload:   180 KB/s\nEnter fullscreen mode\nExit fullscreen mode\nBut this is only the beginning.\nNetwork Connections\nWe can also inspect active network connections:\nconst\nconnections\n=\nawait\nsi\n.\nnetworkConnections\n();\nconsole\n.\nlog\n(\nconnections\n);\nEnter fullscreen mode\nExit fullscreen mode\nThis gives us information about active TCP/UDP connections.\nThat means we can potentially build:\nActive Connections\n────────────────────────────\n\nTCP    443    api.example.com\nTCP    443    github.com\nTCP    443    google.com\nUDP    53     DNS\nEnter fullscreen mode\nExit fullscreen mode\nNow the dashboard starts becoming useful for troubleshooting.\nWhich Application Is Active?\nNetwork traffic alone doesn't tell us\nwho is responsible\n.\nWe also want to know which application the user is interacting with.\nOne option is a native active-window module such as:\nnpm\ninstall\n@jannchie/active-window\nEnter fullscreen mode\nExit fullscreen mode\nThe package exposes information about the currently selected window and user idle time on supported Windows, macOS and Linux/Xorg environments. Platform coverage isn't identical everywhere, so this should be treated as an OS-dependent component.\nConceptually:\nconst\nactiveWindow\n=\nrequire\n(\n\"\n@jannchie/active-window\n\"\n);\nasync\nfunction\ngetActiveApp\n()\n{\nconst\nwindow\n=\nawait\nactiveWindow\n().\ngetActiveWindow\n();\nreturn\n{\nprocess\n:\nwindow\n.\nwindowClass\n,\ntitle\n:\nwindow\n.\nwindowName\n};\n}\nEnter fullscreen mode\nExit fullscreen mode\nThen we can record:\n10:32:14  VS Code\n10:32:15  VS Code\n10:32:16  VS Code\n...\n10:45:02  Chrome\n10:45:03  Chrome\nEnter fullscreen mode\nExit fullscreen mode\nInstead of storing every second individually, we can aggregate this into sessions.\nVS Code\n10:32 → 10:45\nDuration: 13 minutes\nEnter fullscreen mode\nExit fullscreen mode\nSleep and Wake\nThis is where Electron itself becomes useful.\nElectron provides the\npowerMonitor\nmodule for monitoring system power-state changes, including suspend/resume, AC/battery changes, idle state and lock/unlock events on supported platforms.\nIn the Electron main process:\nconst\n{\npowerMonitor\n}\n=\nrequire\n(\n\"\nelectron\n\"\n);\npowerMonitor\n.\non\n(\n\"\nsuspend\n\"\n,\n()\n=>\n{\nconsole\n.\nlog\n(\n\"\nSystem is going to sleep\n\"\n);\n});\npowerMonitor\n.\non\n(\n\"\nresume\n\"\n,\n()\n=>\n{\nconsole\n.\nlog\n(\n\"\nSystem woke up\n\"\n);\n});\nEnter fullscreen mode\nExit fullscreen mode\nWe can turn those events into database records:\nfunction\nrecordEvent\n(\ntype\n)\n{\ndb\n.\ninsert\n({\ntype\n,\ntimestamp\n:\nnew\nDate\n()\n});\n}\npowerMonitor\n.\non\n(\n\"\nsuspend\n\"\n,\n()\n=>\n{\nrecordEvent\n(\n\"\nsystem_sleep\n\"\n);\n});\npowerMonitor\n.\non\n(\n\"\nresume\n\"\n,\n()\n=>\n{\nrecordEvent\n(\n\"\nsystem_wake\n\"\n);\n});\nEnter fullscreen mode\nExit fullscreen mode\nNow our timeline knows when the machine slept.\nIdle Detection\nElectron also provides:\npowerMonitor\n.\ngetSystemIdleTime\n()\nEnter fullscreen mode\nExit fullscreen mode\nand:\npowerMonitor\n.\ngetSystemIdleState\n(\n60\n)\nEnter fullscreen mode\nExit fullscreen mode\nThe latter can return states such as\nactive\n,\nidle\n, or\nlocked\non supported systems.\nFor example:\nconst\nstate\n=\npowerMonitor\n.\ngetSystemIdleState\n(\n60\n);\nconst\nidleSeconds\n=\npowerMonitor\n.\ngetSystemIdleTime\n();\nconsole\n.\nlog\n({\nstate\n,\nidleSeconds\n});\nEnter fullscreen mode\nExit fullscreen mode\nWe can therefore distinguish:\nActive\n   ↓\nIdle\n   ↓\nLocked\n   ↓\nSleep\nEnter fullscreen mode\nExit fullscreen mode\nThat's much more meaningful than simply measuring application runtime.\nPutting Everything Together\nNow imagine collecting events from four sources:\nElectron App\n                      │\n       ┌──────────────┼──────────────┐\n       │              │              │\n  powerMonitor   systeminformation   active-window\n       │              │              │\n       │         CPU / RAM / Net     Active App\n       │              │              │\n       └──────────────┼──────────────┘\n                      ▼\n                Event Collector\n                      │\n                      ▼\n                Local Database\n                      │\n                      ▼\n              Activity Timeline\nEnter fullscreen mode\nExit fullscreen mode\nA simple event model could be:\n{\ntimestamp\n:\nDate\n.\nnow\n(),\ntype\n:\n\"\nnetwork\n\"\n,\napp\n:\n\"\ndocker\n\"\n,\ndownloadBytes\n:\n1824000\n,\nuploadBytes\n:\n420000\n}\nEnter fullscreen mode\nExit fullscreen mode\nOr:\n{\ntimestamp\n:\nDate\n.\nnow\n(),\ntype\n:\n\"\npower\n\"\n,\nstate\n:\n\"\nsleep\n\"\n}\nEnter fullscreen mode\nExit fullscreen mode\nOr:\n{\ntimestamp\n:\nDate\n.\nnow\n(),\ntype\n:\n\"\napplication\n\"\n,\napp\n:\n\"\nCode\n\"\n,\nwindow\n:\n\"\nserver.js - Visual Studio Code\n\"\n}\nEnter fullscreen mode\nExit fullscreen mode\nNow everything becomes queryable.\nThe Interesting Part: Correlation\nThis is where a normal system monitor becomes a\ndesktop observability tool\n.\nSuppose we see:\n18:20  User idle\n18:23  CPU 65%\n18:24  Network 18 MB/s\n18:26  Docker active\n18:31  420 MB downloaded\n18:42  Screen locked\n18:45  Network still active\n19:02  System sleep\nEnter fullscreen mode\nExit fullscreen mode\nWe can generate an explanation:\nDocker continued running after the user became idle and transferred approximately 420 MB before the system entered sleep.\nThat's an insight.\nNot just a metric.\nA Simple Collector\nWe could start with something surprisingly small:\nconst\n{\npowerMonitor\n}\n=\nrequire\n(\n\"\nelectron\n\"\n);\nconst\nsi\n=\nrequire\n(\n\"\nsysteminformation\n\"\n);\nasync\nfunction\ncollect\n()\n{\nconst\n[\ncpu\n,\nmemory\n,\nnetwork\n]\n=\nawait\nPromise\n.\nall\n([\nsi\n.\ncurrentLoad\n(),\nsi\n.\nmem\n(),\nsi\n.\nnetworkStats\n()\n]);\nreturn\n{\ntimestamp\n:\nDate\n.\nnow\n(),\nsystem\n:\n{\nidle\n:\npowerMonitor\n.\ngetSystemIdleTime\n(),\nidleState\n:\npowerMonitor\n.\ngetSystemIdleState\n(\n60\n),\nbattery\n:\npowerMonitor\n.\nisOnBatteryPower\n()\n},\ncpu\n:\ncpu\n.\ncurrentLoad\n,\nmemory\n:\n{\nactive\n:\nmemory\n.\nactive\n,\ntotal\n:\nmemory\n.\ntotal\n},\nnetwork\n:\nnetwork\n.\nmap\n(\nn\n=>\n({\ninterface\n:\nn\n.\niface\n,\nrxPerSecond\n:\nn\n.\nrx_sec\n,\ntxPerSecond\n:\nn\n.\ntx_sec\n}))\n};\n}\nsetInterval\n(\nasync\n()\n=>\n{\nconsole\n.\nlog\n(\nawait\ncollect\n());\n},\n5000\n);\nEnter fullscreen mode\nExit fullscreen mode\nFive seconds later, we have the beginning of our own desktop telemetry system.\nBut Don't Collect Everything Every Second\nThis is an important design decision.\nYou don't necessarily want:\nEvery 1 second\n    ↓\nEverything\n    ↓\nDatabase\nEnter fullscreen mode\nExit fullscreen mode\nThat can generate enormous amounts of useless data.\nInstead, use different collection intervals.\nPower events        Event-driven\nApp changes         Event-driven\nCPU                 5-10 seconds\nMemory              10 seconds\nNetwork             1-5 seconds\nConnections         10-30 seconds\nDaily aggregates    1 minute+\nEnter fullscreen mode\nExit fullscreen mode\nAnd store\nstate changes\nrather than repeated identical states.\nFor example, don't write:\n10:00 VS Code\n10:01 VS Code\n10:02 VS Code\n10:03 VS Code\n10:04 VS Code\nEnter fullscreen mode\nExit fullscreen mode\nStore:\nVS Code\n10:00 → 10:04\nDuration: 4 minutes\nEnter fullscreen mode\nExit fullscreen mode\nThis makes the database dramatically smaller.\nWhat About Electron's Network Logging?\nIf the thing you're monitoring is specifically\nyour Electron application's own network traffic\n, Electron has another useful tool:\nnetLog\n.\nconst\n{\napp\n,\nnetLog\n}\n=\nrequire\n(\n\"\nelectron\n\"\n);\napp\n.\nwhenReady\n().\nthen\n(\nasync\n()\n=>\n{\nawait\nnetLog\n.\nstartLogging\n(\n\"\n/tmp/electron-network-log\n\"\n);\n});\nEnter fullscreen mode\nExit fullscreen mode\nElectron's\nnetLog\nrecords network events for a session and supports different capture modes. Be careful with\nincludeSensitive\nand\neverything\n, because those modes can capture sensitive information or transferred bytes.\nThis is different from\nsysteminformation\n:\nsysteminformation\n        ↓\nWhole-system information\n\nElectron netLog\n        ↓\nYour Electron application's network activity\nEnter fullscreen mode\nExit fullscreen mode\nThat distinction is important.\nThe Product I Would Build\nI wouldn't call this another \"Task Manager.\"\nI'd call it something like:\nDesktop Observability\nThe dashboard could have four major views.\n1. Activity\nVS Code       4h 12m\nChrome        2h 31m\nSlack         58m\nDocker        1h 42m\nEnter fullscreen mode\nExit fullscreen mode\n2. Network\nDownload     8.4 GB\nUpload       1.3 GB\n\nDocker       3.1 GB\nChrome       2.7 GB\nVS Code      1.2 GB\nEnter fullscreen mode\nExit fullscreen mode\n3. Power\nActive       8h 21m\nIdle         1h 08m\nSleep        7h 42m\n\nBattery used 34%\nEnter fullscreen mode\nExit fullscreen mode\n4. Timeline\n09:02  Wake\n09:05  VS Code\n10:32  Chrome\n12:14  Idle\n12:31  Docker network activity\n13:02  Active\n18:42  Lock\n19:01  Sleep\nEnter fullscreen mode\nExit fullscreen mode\nAnd then the killer feature:\nAsk the computer\nWhy was my laptop slow today?\nWhat used the most bandwidth?\nWhat happened while I was away?\nWhich applications are active in the background?\nDid anything continue running after I locked the computer?\nThe Architecture\nA practical first version could be:\nElectron\n│\n├── Main Process\n│   │\n│   ├── powerMonitor\n│   ├── systeminformation\n│   ├── active-window\n│   └── netLog\n│\n├── Collector\n│   │\n│   ├── power events\n│   ├── app sessions\n│   ├── CPU/memory\n│   └── network stats\n│\n├── SQLite\n│\n└── React Renderer\n    │\n    ├── Dashboard\n    ├── Timeline\n    ├── Applications\n    ├── Network\n    └── Power\nEnter fullscreen mode\nExit fullscreen mode\nThe key design principle is:\nCollect locally. Correlate locally. Explain locally.\nYou don't need to send a user's entire computer activity to a cloud server just to tell them why their laptop was busy.\nThe Bigger Idea\nThe interesting thing here isn't\nsysteminformation\n.\nIt isn't Electron's\npowerMonitor\n.\nIt isn't network monitoring.\nIt's the\ncombination\n.\nA computer is already generating thousands of signals:\nApplications\nProcesses\nCPU\nMemory\nNetwork\nPower\nSleep\nWake\nIdle\nLock\nUnlock\nEnter fullscreen mode\nExit fullscreen mode\nToday, these signals are mostly presented independently.\nA desktop observability layer could connect them.\nAnd instead of asking:\n\"What's my CPU usage?\"\nwe can finally ask:\n\"What happened on my computer while I wasn't looking?\"\nThat's a much more interesting monitoring problem.",
  "metadata": {
    "title": "Your Computer Is Busy When You're Not: Building a Desktop Observability Tool with Electron - DEV Community",
    "description": "You close your laptop.  The screen goes dark.  You assume everything stopped.  But what if it... Tagged with electron, desktopapps, power, tooling.",
    "keywords": "electron, desktopapps, power, tooling, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/nishikantaray/your-computer-is-busy-when-youre-not-building-a-desktop-observability-tool-with-electron-1m6k",
    "og:title": "Your Computer Is Busy When You're Not: Building a Desktop Observability Tool with Electron",
    "og:description": "You close your laptop.  The screen goes dark.  You assume everything stopped.  But what if it...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjt22b4gp6sw3zyd7ikh.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "Your Computer Is Busy When You're Not: Building a Desktop Observability Tool with Electron",
    "twitter:description": "You close your laptop.  The screen goes dark.  You assume everything stopped.  But what if it...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjt22b4gp6sw3zyd7ikh.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/64901f7bc16c/code/body-000.txt",
      "chars": 524,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-001.txt",
      "chars": 137,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-002.txt",
      "chars": 30,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-003.txt",
      "chars": 372,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-004.txt",
      "chars": 18,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-005.txt",
      "chars": 276,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-006.txt",
      "chars": 44,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-007.txt",
      "chars": 78,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-008.txt",
      "chars": 147,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-009.txt",
      "chars": 36,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-010.txt",
      "chars": 228,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-011.txt",
      "chars": 92,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-012.txt",
      "chars": 43,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-013.txt",
      "chars": 201,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-014.txt",
      "chars": 227,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-015.txt",
      "chars": 33,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-016.txt",
      "chars": 36,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-017.txt",
      "chars": 147,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-018.txt",
      "chars": 40,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-019.txt",
      "chars": 563,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-020.txt",
      "chars": 113,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-021.txt",
      "chars": 63,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-022.txt",
      "chars": 110,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-023.txt",
      "chars": 170,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-024.txt",
      "chars": 745,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-025.txt",
      "chars": 47,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-026.txt",
      "chars": 226,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-027.txt",
      "chars": 70,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-028.txt",
      "chars": 42,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-029.txt",
      "chars": 143,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-030.txt",
      "chars": 125,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-031.txt",
      "chars": 81,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-032.txt",
      "chars": 101,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-033.txt",
      "chars": 78,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-034.txt",
      "chars": 123,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-035.txt",
      "chars": 349,
      "language": "text"
    },
    {
      "file": "attachments/64901f7bc16c/code/body-036.txt",
      "chars": 76,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:50Z",
  "source_url": "https://dev.to/nishikantaray/your-computer-is-busy-when-youre-not-building-a-desktop-observability-tool-with-electron-1m6k",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 13275,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/64901f7bc16c/pages/source.html",
  "local_html": "attachments/64901f7bc16c/pages/article.html",
  "body_base_path": "attachments/64901f7bc16c/pages/",
  "body_markdown_path": "attachments/64901f7bc16c/pages/article.md"
}