{"id":1100,"date":"2026-07-06T13:16:42","date_gmt":"2026-07-06T13:16:42","guid":{"rendered":"https:\/\/guoh.ai\/lifelog\/?p=1100"},"modified":"2026-07-06T20:24:28","modified_gmt":"2026-07-06T20:24:28","slug":"dialf-drive-a-real-phone-from-your-terminal","status":"publish","type":"post","link":"https:\/\/guoh.ai\/lifelog\/2026\/07\/dialf-drive-a-real-phone-from-your-terminal\/","title":{"rendered":"DialF: Drive a Real Phone From Your Terminal"},"content":{"rendered":"<div class=\"gfmr-markdown-container\"><div class=\"gfmr-markdown-source\" style=\"display: none;\">*A small tool that lets a script place real phone calls, talk, listen, and hang up \u2014 on a real SIM, over a real cellular network.*\n\n&#8212;\n\n## Why we built this\n\nAI voice agents are everywhere now \u2014 and they **live and die by latency and audio quality.** A second of dead air, a stiff robotic voice, or choppy, fluctuating audio is the difference between &quot;sounds human&quot; and &quot;obviously a bot.&quot; Yet before every release we were measuring those things *by hand*: dial in, read a script, listen for gaps, do it again on the next build. It didn&#039;t scale, and &quot;sounds fine to me&quot; is not a regression test.\n\nWhat we actually needed was to automate a *real* phone call. Not a VoIP call. Not a simulator. An actual call on an actual carrier \u2014 the kind that rings a normal phone, goes through the normal network, and behaves exactly like a human dialing. So we could:\n\n&#8211; **Test phone systems end to end** \u2014 voice agents, IVRs, call centers, voicemail \u2014 the way a real caller experiences them.\n&#8211; **Run scripted conversations** \u2014 play a prompt, wait for the other side to finish talking, play the next one.\n&#8211; **Record both sides cleanly, on one timeline**, so we could measure **latency** (&quot;how long after I speak does the other side respond?&quot;) \u2014 as a number, on every build.\n\nThe catch: **Android won&#039;t let an app record or inject the audio of a cellular call.** That path is locked to the system. So a pure software approach is impossible.\n\nDialF&#039;s answer is simple and a little old-school: **bridge the call audio through a real USB sound card.** The phone does the dialing; a sound card plays into the phone&#039;s mic and listens on its earpiece. Your computer drives the whole thing \u2014 and we know you&#039;ll wire your own AI agents up to do the driving.\n\n&#8212;\n\n## Why not a programmable 4G module?\n\nIt&#039;s the first thing every engineer suggests, and it&#039;s a fair instinct \u2014 a cellular module takes a SIM, speaks AT commands, and dials from a script. Cheap, headless, no human in the loop.\n\nBut a module isn&#039;t a phone. It carries its own compatibility quirks and behaves in ways real handsets don&#039;t \u2014 so it can quietly alter the very thing you&#039;re trying to measure. You end up testing the module&#039;s behavior, not your users&#039; calls.\n\nThat&#039;s the crux: **a voice agent&#039;s audio path *is* the product, and a module only tests a synthetic version of it.** Your agent can sound flawless through a module and still ship stutter and echo through a real earpiece \u2014 and the module never warns you, because it was never on the path your callers actually hear. DialF drives a real phone for exactly that reason.\n\n&#8212;\n\n## What it does\n\nDialF turns a phone into something you can script:\n\n&#8211; \ud83d\udcde **Make, answer, reject, and hang up calls** \u2014 on the phone&#039;s own SIM.\n&#8211; \ud83d\udcac **Send and read SMS**, read the **call log** and **SIM list** (dual-SIM aware).\n&#8211; \ud83c\udf9b\ufe0f **Carrier controls** \u2014 toggle voicemail, run raw MMI\/USSD codes.\n&#8211; \ud83d\udde3\ufe0f **Scripted voice conversations** \u2014 play audio prompts, and *wait for the person to stop talking* using voice-activity detection before moving on.\n&#8211; \ud83c\udf99\ufe0f **Record the call** full-duplex \u2014 your audio (`tx`), their audio (`rx`), and a **stereo mix** (left = you, right = them), all the same length and sample-aligned (great for latency analysis).\n\nYou drive all of it from one command-line tool, or from a small YAML script.\n\n&#8212;\n\n## How it works\n\nDialF has two parts that talk to each other, plus a deliberate split between **control** and **audio**:\n\n&#8220;`mermaid\nflowchart TB\n  subgraph Host[&quot;Your computer&quot;]\n    CLI[&quot;dialf (CLI)&quot;]\n    D[&quot;dialfd (daemon)&quot;]\n    Card[&quot;USB sound card&quot;]\n  end\n  subgraph Android[&quot;Android phone&quot;]\n    App[&quot;DialF Phone app&quot;]\n  end\n\n  CLI &#8211;&gt;|commands| D\n  D &lt;&#8211;&gt;|&quot;WiFi \u00b7 WebSocket&quot;| App\n  D &lt;&#8211;&gt;|&quot;audio in \/ out&quot;| Card\n  Card &lt;&#8211;&gt;|&quot;headset cable&quot;| App\n  App &#8211;&gt;|&quot;dials \/ answers on its SIM&quot;| Net((&quot;Cellular network&quot;))\n&#8220;`\n\n&#8211; **Control plane (over WiFi):** the `dialf` CLI sends commands to the `dialfd` daemon, which relays them to the **DialF Phone** app over a WebSocket. This is how dial \/ answer \/ SMS \/ hang up happen. No audio travels here.\n&#8211; **Audio plane (physical):** call audio flows through a **USB sound card** wired to the phone&#039;s headset jack. The card plays *into* the phone&#039;s microphone and records *from* its earpiece. The app just routes the call to the wired headset.\n\nWhy the split? Because Android blocks call-audio capture in software \u2014 so audio has to be bridged physically, never over WiFi.\n\n### A scripted call, step by step\n\n&#8220;`mermaid\nsequenceDiagram\n  participant CLI as dialf CLI\n  participant D as dialfd\n  participant P as DialF Phone\n  participant F as Far end\n\n  CLI-&gt;&gt;D: run call-script.yaml\n  D-&gt;&gt;P: dial +1&#8230;\n  P-&gt;&gt;F: ringing\u2026\n  F&#8211;&gt;&gt;P: answers\n  P&#8211;&gt;&gt;D: call active\n  Note over D: call.wait_answered satisfied\n  D-&gt;&gt;P: play prompt (out the sound card \u2192 phone mic)\n  P-&gt;&gt;F: far end hears the prompt\n  F&#8211;&gt;&gt;P: spoken reply (phone earpiece \u2192 sound card)\n  P&#8211;&gt;&gt;D: reply audio captured\n  Note over D: VAD waits for the reply to finish\n  D-&gt;&gt;P: play next prompt \u2026 then hang up\n&#8220;`\n\n&#8212;\n\n## How to use it\n\n### 1. Install the CLI (macOS or Linux)\n\n&#8220;`sh\nnpm install -g @agora-build\/dialf\n# or:  curl -fsSL https:\/\/dl.agora.build\/dialf\/install.sh | bash\n&#8220;`\n\nThen start the background daemon:\n\n&#8220;`sh\ndialf service install &#8211;user      # runs dialfd at login\n&#8220;`\n\n&gt; On a Mac or laptop, keep `&#8211;user` \u2014 it runs as you, when you log in (needed so it can reach the sound card and mic). Use plain `dialf service install` (with `sudo`) only on a headless Linux server that should start at boot.\n\n### 2. Install the phone app\n\nSideload the APK on the Android phone (Android 9+):\n\n&#8211; **Latest release (default):** &lt;https:\/\/github.com\/Agora-Build\/DialF\/releases&gt;\n&#8211; or &lt;https:\/\/dl.agora.build\/dialf\/dialf-phone-latest.apk&gt;\n\nOpen it, grant phone\/SMS permissions, and **set it as the default dialer** (that&#039;s what lets it place and track calls).\n\n### 3. Pair them\n\nIn the app, enter the same **shared key** as your `dialfd` config and tap **Start service**. The phone finds the daemon automatically on your WiFi (mDNS). Confirm it&#039;s connected:\n\n&#8220;`sh\ndialf devices        # your phone should appear\n&#8220;`\n\n### 4. Drive it\n\n&#8220;`sh\ndialf call dial   &lt;phone&gt; +15551234        # place a call\ndialf call hangup &lt;phone&gt;                  # hang up\ndialf sms  send   &lt;phone&gt; +15551234 &quot;hi&quot;   # send a text\ndialf call list   &lt;phone&gt; &#8211;human          # read the call log\ndialf &#8211;version                            # CLI + daemon versions\n&#8220;`\n\n### 5. Script a conversation\n\nJobs are plain YAML \u2014 a list of steps run in order:\n\n&#8220;`yaml\n&#8211; type: call.dial\n  number: &quot;+15551234&quot;\n&#8211; type: call.wait_answered      # wait for a real answer, not a fixed timer\n  timeout_ms: 30000\n&#8211; type: audio.play              # inject a prompt into the call\n  file: samples\/prompt-en-1.wav\n&#8211; type: audio.wait_for_speech   # listen until the other side stops talking\n  end_timeout_ms: 45000\n  silence_duration_ms: 3000\n&#8211; type: sms.send\n  to: &quot;+15551234&quot;\n  body: &quot;thanks!&quot;\n&#8211; type: call.hangup\n&#8220;`\n\n&#8220;`sh\ndialf run call-script.yaml\n&#8220;`\n\n`audio.wait_for_speech` is the clever bit: it runs voice-activity detection on the incoming audio, so the script moves on **when the person actually finishes speaking** \u2014 not after a guess.\n\n&#8212;\n\n## Recording and latency\n\nIf you turn on recording, every call is written as **three aligned WAV files**:\n\n&#8211; `\u2026-tx.wav` \u2014 what you sent (your prompts), mono\n&#8211; `\u2026-rx.wav` \u2014 what the far end said, mono\n&#8211; `\u2026-mix.wav` \u2014 **stereo**: left = tx (you), right = rx (them), so the two voices stay separated (swap with `mix_channels: rx_tx`)\n\nThey&#039;re captured on a **single clock**, so they line up sample-for-sample. That makes latency measurable: cross-correlate `tx` against `rx` and the offset is your round-trip delay.\n\n&#8220;`mermaid\nflowchart LR\n  TX[&quot;tx.wav \u00b7 your prompt&quot;] &#8211;&gt; MIX[&quot;mix.wav \u00b7 stereo \u00b7 L=tx R=rx&quot;]\n  RX[&quot;rx.wav \u00b7 far-end reply&quot;] &#8211;&gt; MIX\n  TX -.-&gt;|&quot;cross-correlate&quot;| RX\n  RX &#8211;&gt; L[&quot;latency = the lag between them&quot;]\n&#8220;`\n\n&#8212;\n\n## Wrapping up\n\nDialF is a thin, scriptable bridge between your terminal and a real phone. The control side is clean software over WiFi; the audio side is honest about hardware \u2014 a sound card doing what software isn&#039;t allowed to. Together they let a few lines of YAML place a call, hold a conversation, and hand you a clean recording.\n\nIt runs on macOS and Linux, the CLI installs from npm, and the phone app is a sideloadable APK. If you&#039;ve ever wanted to put a real phone call inside a `for` loop \u2014 that&#039;s the idea.\n\n&#8212;\n\n## License\n\nDialF is released under the **MIT License**.\n\n**Disclaimer:** This tool is strictly for engineering use only and must not be used for any illegal purposes. The user bears all legal consequences arising from its use.\n<\/div><div class=\"gfmr-markdown-rendered\" data-mermaid-bg-color=\"transparent\" data-shiki-theme=\"github-dark\"><p><em>A small tool that lets a script place real phone calls, talk, listen, and hang up \u2014 on a real SIM, over a real cellular network.<\/em><\/p>\n<hr>\n<h2>Why we built this<\/h2>\n<p>AI voice agents are everywhere now \u2014 and they <strong>live and die by latency and audio quality.<\/strong> A second of dead air, a stiff robotic voice, or choppy, fluctuating audio is the difference between \u201csounds human\u201d and \u201cobviously a bot.\u201d Yet before every release we were measuring those things <em>by hand<\/em>: dial in, read a script, listen for gaps, do it again on the next build. It didn\u2019t scale, and \u201csounds fine to me\u201d is not a regression test.<\/p>\n<p>What we actually needed was to automate a <em>real<\/em> phone call. Not a VoIP call. Not a simulator. An actual call on an actual carrier \u2014 the kind that rings a normal phone, goes through the normal network, and behaves exactly like a human dialing. So we could:<\/p>\n<ul>\n<li><strong>Test phone systems end to end<\/strong> \u2014 voice agents, IVRs, call centers, voicemail \u2014 the way a real caller experiences them.<\/li>\n<li><strong>Run scripted conversations<\/strong> \u2014 play a prompt, wait for the other side to finish talking, play the next one.<\/li>\n<li><strong>Record both sides cleanly, on one timeline<\/strong>, so we could measure <strong>latency<\/strong> (\u201chow long after I speak does the other side respond?\u201d) \u2014 as a number, on every build.<\/li>\n<\/ul>\n<p>The catch: <strong>Android won\u2019t let an app record or inject the audio of a cellular call.<\/strong> That path is locked to the system. So a pure software approach is impossible.<\/p>\n<p>DialF\u2019s answer is simple and a little old-school: <strong>bridge the call audio through a real USB sound card.<\/strong> The phone does the dialing; a sound card plays into the phone\u2019s mic and listens on its earpiece. Your computer drives the whole thing \u2014 and we know you\u2019ll wire your own AI agents up to do the driving.<\/p>\n<hr>\n<h2>Why not a programmable 4G module?<\/h2>\n<p>It\u2019s the first thing every engineer suggests, and it\u2019s a fair instinct \u2014 a cellular module takes a SIM, speaks AT commands, and dials from a script. Cheap, headless, no human in the loop.<\/p>\n<p>But a module isn\u2019t a phone. It carries its own compatibility quirks and behaves in ways real handsets don\u2019t \u2014 so it can quietly alter the very thing you\u2019re trying to measure. You end up testing the module\u2019s behavior, not your users\u2019 calls.<\/p>\n<p>That\u2019s the crux: <strong>a voice agent\u2019s audio path <em>is<\/em> the product, and a module only tests a synthetic version of it.<\/strong> Your agent can sound flawless through a module and still ship stutter and echo through a real earpiece \u2014 and the module never warns you, because it was never on the path your callers actually hear. DialF drives a real phone for exactly that reason.<\/p>\n<hr>\n<h2>What it does<\/h2>\n<p>DialF turns a phone into something you can script:<\/p>\n<ul>\n<li>\ud83d\udcde <strong>Make, answer, reject, and hang up calls<\/strong> \u2014 on the phone\u2019s own SIM.<\/li>\n<li>\ud83d\udcac <strong>Send and read SMS<\/strong>, read the <strong>call log<\/strong> and <strong>SIM list<\/strong> (dual-SIM aware).<\/li>\n<li>\ud83c\udf9b\ufe0f <strong>Carrier controls<\/strong> \u2014 toggle voicemail, run raw MMI\/USSD codes.<\/li>\n<li>\ud83d\udde3\ufe0f <strong>Scripted voice conversations<\/strong> \u2014 play audio prompts, and <em>wait for the person to stop talking<\/em> using voice-activity detection before moving on.<\/li>\n<li>\ud83c\udf99\ufe0f <strong>Record the call<\/strong> full-duplex \u2014 your audio (<code>tx<\/code>), their audio (<code>rx<\/code>), and a <strong>stereo mix<\/strong> (left = you, right = them), all the same length and sample-aligned (great for latency analysis).<\/li>\n<\/ul>\n<p>You drive all of it from one command-line tool, or from a small YAML script.<\/p>\n<hr>\n<h2>How it works<\/h2>\n<p>DialF has two parts that talk to each other, plus a deliberate split between <strong>control<\/strong> and <strong>audio<\/strong>:<\/p>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span>flowchart TB<\/span><\/span>\n<span class=\"line\"><span>  subgraph Host[\"Your computer\"]<\/span><\/span>\n<span class=\"line\"><span>    CLI[\"dialf (CLI)\"]<\/span><\/span>\n<span class=\"line\"><span>    D[\"dialfd (daemon)\"]<\/span><\/span>\n<span class=\"line\"><span>    Card[\"USB sound card\"]<\/span><\/span>\n<span class=\"line\"><span>  end<\/span><\/span>\n<span class=\"line\"><span>  subgraph Android[\"Android phone\"]<\/span><\/span>\n<span class=\"line\"><span>    App[\"DialF Phone app\"]<\/span><\/span>\n<span class=\"line\"><span>  end<\/span><\/span>\n<span class=\"line\"><span><\/span><\/span>\n<span class=\"line\"><span>  CLI --&gt;|commands| D<\/span><\/span>\n<span class=\"line\"><span>  D &#x3C;--&gt;|\"WiFi \u00b7 WebSocket\"| App<\/span><\/span>\n<span class=\"line\"><span>  D &#x3C;--&gt;|\"audio in \/ out\"| Card<\/span><\/span>\n<span class=\"line\"><span>  Card &#x3C;--&gt;|\"headset cable\"| App<\/span><\/span>\n<span class=\"line\"><span>  App --&gt;|\"dials \/ answers on its SIM\"| Net((\"Cellular network\"))<\/span><\/span>\n<span class=\"line\"><span><\/span><\/span><\/code><\/pre>\n<ul>\n<li><strong>Control plane (over WiFi):<\/strong> the <code>dialf<\/code> CLI sends commands to the <code>dialfd<\/code> daemon, which relays them to the <strong>DialF Phone<\/strong> app over a WebSocket. This is how dial \/ answer \/ SMS \/ hang up happen. No audio travels here.<\/li>\n<li><strong>Audio plane (physical):<\/strong> call audio flows through a <strong>USB sound card<\/strong> wired to the phone\u2019s headset jack. The card plays <em>into<\/em> the phone\u2019s microphone and records <em>from<\/em> its earpiece. The app just routes the call to the wired headset.<\/li>\n<\/ul>\n<p>Why the split? Because Android blocks call-audio capture in software \u2014 so audio has to be bridged physically, never over WiFi.<\/p>\n<h3>A scripted call, step by step<\/h3>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span>sequenceDiagram<\/span><\/span>\n<span class=\"line\"><span>  participant CLI as dialf CLI<\/span><\/span>\n<span class=\"line\"><span>  participant D as dialfd<\/span><\/span>\n<span class=\"line\"><span>  participant P as DialF Phone<\/span><\/span>\n<span class=\"line\"><span>  participant F as Far end<\/span><\/span>\n<span class=\"line\"><span><\/span><\/span>\n<span class=\"line\"><span>  CLI-&gt;&gt;D: run call-script.yaml<\/span><\/span>\n<span class=\"line\"><span>  D-&gt;&gt;P: dial +1...<\/span><\/span>\n<span class=\"line\"><span>  P-&gt;&gt;F: ringing\u2026<\/span><\/span>\n<span class=\"line\"><span>  F--&gt;&gt;P: answers<\/span><\/span>\n<span class=\"line\"><span>  P--&gt;&gt;D: call active<\/span><\/span>\n<span class=\"line\"><span>  Note over D: call.wait_answered satisfied<\/span><\/span>\n<span class=\"line\"><span>  D-&gt;&gt;P: play prompt (out the sound card \u2192 phone mic)<\/span><\/span>\n<span class=\"line\"><span>  P-&gt;&gt;F: far end hears the prompt<\/span><\/span>\n<span class=\"line\"><span>  F--&gt;&gt;P: spoken reply (phone earpiece \u2192 sound card)<\/span><\/span>\n<span class=\"line\"><span>  P--&gt;&gt;D: reply audio captured<\/span><\/span>\n<span class=\"line\"><span>  Note over D: VAD waits for the reply to finish<\/span><\/span>\n<span class=\"line\"><span>  D-&gt;&gt;P: play next prompt \u2026 then hang up<\/span><\/span>\n<span class=\"line\"><span><\/span><\/span><\/code><\/pre>\n<hr>\n<h2>How to use it<\/h2>\n<h3>1. Install the CLI (macOS or Linux)<\/h3>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#B392F0\">npm<\/span><span style=\"color:#9ECBFF\"> install<\/span><span style=\"color:#79B8FF\"> -g<\/span><span style=\"color:#9ECBFF\"> @agora-build\/dialf<\/span><\/span>\n<span class=\"line\"><span style=\"color:#6A737D\"># or:  curl -fsSL https:\/\/dl.agora.build\/dialf\/install.sh | bash<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<p>Then start the background daemon:<\/p>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> service<\/span><span style=\"color:#9ECBFF\"> install<\/span><span style=\"color:#79B8FF\"> --user<\/span><span style=\"color:#6A737D\">      # runs dialfd at login<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<blockquote>\n<p>On a Mac or laptop, keep <code>--user<\/code> \u2014 it runs as you, when you log in (needed so it can reach the sound card and mic). Use plain <code>dialf service install<\/code> (with <code>sudo<\/code>) only on a headless Linux server that should start at boot.<\/p>\n<\/blockquote>\n<h3>2. Install the phone app<\/h3>\n<p>Sideload the APK on the Android phone (Android 9+):<\/p>\n<ul>\n<li><strong>Latest release (default):<\/strong> <a href=\"https:\/\/github.com\/Agora-Build\/DialF\/releases\">https:\/\/github.com\/Agora-Build\/DialF\/releases<\/a><\/li>\n<li>or <a href=\"https:\/\/dl.agora.build\/dialf\/dialf-phone-latest.apk\">https:\/\/dl.agora.build\/dialf\/dialf-phone-latest.apk<\/a><\/li>\n<\/ul>\n<p>Open it, grant phone\/SMS permissions, and <strong>set it as the default dialer<\/strong> (that\u2019s what lets it place and track calls).<\/p>\n<h3>3. Pair them<\/h3>\n<p>In the app, enter the same <strong>shared key<\/strong> as your <code>dialfd<\/code> config and tap <strong>Start service<\/strong>. The phone finds the daemon automatically on your WiFi (mDNS). Confirm it\u2019s connected:<\/p>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> devices<\/span><span style=\"color:#6A737D\">        # your phone should appear<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<h3>4. Drive it<\/h3>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> call<\/span><span style=\"color:#9ECBFF\"> dial<\/span><span style=\"color:#F97583\">   &#x3C;<\/span><span style=\"color:#9ECBFF\">phon<\/span><span style=\"color:#E1E4E8\">e<\/span><span style=\"color:#F97583\">&gt;<\/span><span style=\"color:#9ECBFF\"> +15551234<\/span><span style=\"color:#6A737D\">        # place a call<\/span><\/span>\n<span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> call<\/span><span style=\"color:#9ECBFF\"> hangup<\/span><span style=\"color:#F97583\"> &#x3C;<\/span><span style=\"color:#9ECBFF\">phon<\/span><span style=\"color:#E1E4E8\">e<\/span><span style=\"color:#F97583\">&gt;<\/span><span style=\"color:#6A737D\">                  # hang up<\/span><\/span>\n<span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> sms<\/span><span style=\"color:#9ECBFF\">  send<\/span><span style=\"color:#F97583\">   &#x3C;<\/span><span style=\"color:#9ECBFF\">phon<\/span><span style=\"color:#E1E4E8\">e<\/span><span style=\"color:#F97583\">&gt;<\/span><span style=\"color:#9ECBFF\"> +15551234<\/span><span style=\"color:#9ECBFF\"> \"hi\"<\/span><span style=\"color:#6A737D\">   # send a text<\/span><\/span>\n<span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> call<\/span><span style=\"color:#9ECBFF\"> list<\/span><span style=\"color:#F97583\">   &#x3C;<\/span><span style=\"color:#9ECBFF\">phon<\/span><span style=\"color:#E1E4E8\">e<\/span><span style=\"color:#F97583\">&gt;<\/span><span style=\"color:#79B8FF\"> --human<\/span><span style=\"color:#6A737D\">          # read the call log<\/span><\/span>\n<span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#79B8FF\"> --version<\/span><span style=\"color:#6A737D\">                            # CLI + daemon versions<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<h3>5. Script a conversation<\/h3>\n<p>Jobs are plain YAML \u2014 a list of steps run in order:<\/p>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">call.dial<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  number<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">\"+15551234\"<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">call.wait_answered<\/span><span style=\"color:#6A737D\">      # wait for a real answer, not a fixed timer<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  timeout_ms<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#79B8FF\">30000<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">audio.play<\/span><span style=\"color:#6A737D\">              # inject a prompt into the call<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  file<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">samples\/prompt-en-1.wav<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">audio.wait_for_speech<\/span><span style=\"color:#6A737D\">   # listen until the other side stops talking<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  end_timeout_ms<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#79B8FF\">45000<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  silence_duration_ms<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#79B8FF\">3000<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">sms.send<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  to<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">\"+15551234\"<\/span><\/span>\n<span class=\"line\"><span style=\"color:#85E89D\">  body<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">\"thanks!\"<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">- <\/span><span style=\"color:#85E89D\">type<\/span><span style=\"color:#E1E4E8\">: <\/span><span style=\"color:#9ECBFF\">call.hangup<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span style=\"color:#B392F0\">dialf<\/span><span style=\"color:#9ECBFF\"> run<\/span><span style=\"color:#9ECBFF\"> call-script.yaml<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre>\n<p><code>audio.wait_for_speech<\/code> is the clever bit: it runs voice-activity detection on the incoming audio, so the script moves on <strong>when the person actually finishes speaking<\/strong> \u2014 not after a guess.<\/p>\n<hr>\n<h2>Recording and latency<\/h2>\n<p>If you turn on recording, every call is written as <strong>three aligned WAV files<\/strong>:<\/p>\n<ul>\n<li><code>\u2026-tx.wav<\/code> \u2014 what you sent (your prompts), mono<\/li>\n<li><code>\u2026-rx.wav<\/code> \u2014 what the far end said, mono<\/li>\n<li><code>\u2026-mix.wav<\/code> \u2014 <strong>stereo<\/strong>: left = tx (you), right = rx (them), so the two voices stay separated (swap with <code>mix_channels: rx_tx<\/code>)<\/li>\n<\/ul>\n<p>They\u2019re captured on a <strong>single clock<\/strong>, so they line up sample-for-sample. That makes latency measurable: cross-correlate <code>tx<\/code> against <code>rx<\/code> and the offset is your round-trip delay.<\/p>\n<pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\"><code><span class=\"line\"><span>flowchart LR<\/span><\/span>\n<span class=\"line\"><span>  TX[\"tx.wav \u00b7 your prompt\"] --&gt; MIX[\"mix.wav \u00b7 stereo \u00b7 L=tx R=rx\"]<\/span><\/span>\n<span class=\"line\"><span>  RX[\"rx.wav \u00b7 far-end reply\"] --&gt; MIX<\/span><\/span>\n<span class=\"line\"><span>  TX -.-&gt;|\"cross-correlate\"| RX<\/span><\/span>\n<span class=\"line\"><span>  RX --&gt; L[\"latency = the lag between them\"]<\/span><\/span>\n<span class=\"line\"><span><\/span><\/span><\/code><\/pre>\n<hr>\n<h2>Wrapping up<\/h2>\n<p>DialF is a thin, scriptable bridge between your terminal and a real phone. The control side is clean software over WiFi; the audio side is honest about hardware \u2014 a sound card doing what software isn\u2019t allowed to. Together they let a few lines of YAML place a call, hold a conversation, and hand you a clean recording.<\/p>\n<p>It runs on macOS and Linux, the CLI installs from npm, and the phone app is a sideloadable APK. If you\u2019ve ever wanted to put a real phone call inside a <code>for<\/code> loop \u2014 that\u2019s the idea.<\/p>\n<hr>\n<h2>License<\/h2>\n<p>DialF is released under the <strong>MIT License<\/strong>.<\/p>\n<p><strong>Disclaimer:<\/strong> This tool is strictly for engineering use only and must not be used for any illegal purposes. The user bears all legal consequences arising from its use.<\/p>\n<\/div><\/div>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_gfmr_meta_descriptions":[],"_gfmr_multilingual_taxonomy_terms":[],"footnotes":""},"categories":[7,1,9],"tags":[24],"class_list":["post-1100","post","type-post","status-publish","format-standard","hentry","category-android","category-misc","category-tech","tag-tool"],"_links":{"self":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/1100","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/comments?post=1100"}],"version-history":[{"count":21,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/1100\/revisions"}],"predecessor-version":[{"id":1136,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/1100\/revisions\/1136"}],"wp:attachment":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/media?parent=1100"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/categories?post=1100"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/tags?post=1100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}