<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Projects on Ash Tewari</title><link>https://www.tewari.info/category/projects/</link><description>Recent content in Projects on Ash Tewari</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 08 Oct 2026 01:38:44 +0000</lastBuildDate><atom:link href="https://www.tewari.info/category/projects/feed.xml" rel="self" type="application/rss+xml"/><item><title>Who needs Surface Ultra RTX?</title><link>https://www.tewari.info/2026/10/07/who-needs-surface-ultra-rtx/</link><pubDate>Thu, 08 Oct 2026 01:38:44 +0000</pubDate><guid>https://www.tewari.info/2026/10/07/who-needs-surface-ultra-rtx/</guid><description>&lt;p&gt;Microsoft announced the new &lt;a class="link" href="https://www.microsoft.com/en-us/surface/devices/surface-laptop-ultra" target="_blank" rel="noopener"&#10; &gt;Surface Ultra RTX Laptop&lt;/a&gt; today. Of course I want the RTX Spark Machine! Who doesn&amp;rsquo;t? It is awesome! I am being nostalgic about my original Microsoft Surface Pro from 2013 though. I have fond memories of developing and testing &lt;a class="link" href="https://www.tewari.info/tag/windows-8/" &gt;Windows 8&lt;/a&gt; &lt;a class="link" href="https://www.tewari.info/tag/xaml/" &gt;Apps&lt;/a&gt; and &lt;a class="link" href="https://www.tewari.info/category/xamarin/" &gt;Windows&lt;/a&gt; &lt;a class="link" href="https://www.tewari.info/tag/windows-phone/" &gt;Phone&lt;/a&gt; &lt;a class="link" href="https://www.tewari.info/tag/wp7/" &gt;Apps&lt;/a&gt;(remember those?) on it.&lt;/p&gt;&#10;&lt;p&gt;The spec sheet of my &lt;a class="link" href="https://en.wikipedia.org/wiki/Surface_Pro_%281st_generation%29" target="_blank" rel="noopener"&#10; &gt;Surface Pro 1 from 2013&lt;/a&gt; is ancient: 3rd-gen Intel Core i5, 4 GB of RAM, 128 GB SSD. Windows support ended years ago.&lt;/p&gt;&#10;&lt;p&gt;I decided to wipe it and give it a new life with &lt;a class="link" href="https://xubuntu.org" target="_blank" rel="noopener"&#10; &gt;xubuntu&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://xubuntu.org/releasedocs/26.04/release-notes/" target="_blank" rel="noopener"&#10; &gt;xubuntu 26.04&lt;/a&gt; refused to boot at first. The culprit turned out to be the installation media rather than Linux: the Surface was hitting USB enumeration and read errors with my original flash drive. Safe graphics made no difference. A different stick, USB 2.0 this time, dropped me straight into the live environment. Live environment was extremely choppy and slow. I was able to complete some basic validations. I settled on &lt;a class="link" href="https://xubuntu.org/releasedocs/24.04/release-notes/" target="_blank" rel="noopener"&#10; &gt;xubuntu 24.04&lt;/a&gt; LTS for the permanent install, and I will revisit 26.04 now that the basic setup works.&lt;/p&gt;&#10;&lt;p&gt;Then the machine turned out to be more usable than I expected. Wi-Fi, Type Cover, and the Touch Screen all worked, and Firefox, Git, Python, terminals, SSH and lightweight dev tooling all run fine. XFCE and Ubuntu&amp;rsquo;s package ecosystem give me a current Linux environment that still leaves room to work in 4 GB of RAM. The Surface Pro&amp;rsquo;s compact form factor makes it light and portable next to my Lenovo Legion behemoth. The whole Surface Pro is probably lighter than the Legion&amp;rsquo;s power brick.&lt;/p&gt;&#10;&lt;p&gt;I am not going to run local LLMs, a Kubernetes cluster or a dozen containers on it. As a small Linux terminal, SSH client, coding machine, browser or home-lab console, it has plenty of life left.&lt;/p&gt;&#10;&lt;p&gt;I will be using it as a host for coding agents. The model runs in another local headless server (more on that in another blog post) or in the cloud, so what is left on this machine is an orchestration loop, HTTP calls, a git checkout and a shell, over &lt;a class="link" href="https://tailscale.com/" target="_blank" rel="noopener"&#10; &gt;tailscale&lt;/a&gt;. That is an I/O problem, and a 2013 i5 should do just fine. Headless over SSH, 4 GB should be enough. I&amp;rsquo;ll find out. The other appeal is blast radius. An agent that runs shell commands gets a cheap, disposable machine of its own instead of my main one.&lt;/p&gt;&#10;&lt;p&gt;There is something satisfying about taking hardware at the end of its commercial life and saving it from landfill.&lt;/p&gt;&#10;</description></item><item><title>Agentic finetuning using a custom harness</title><link>https://www.tewari.info/2026/09/24/agentic-finetuning-using-a-custom-harness/</link><pubDate>Fri, 25 Sep 2026 02:56:11 +0000</pubDate><guid>https://www.tewari.info/2026/09/24/agentic-finetuning-using-a-custom-harness/</guid><description>&lt;p&gt;The goal was to figure out whether a small model running on my own hardware could do the job of intent classification and structured parameter extraction  well enough for one of my use-cases - read a request like &amp;ldquo;add 8 spare tent stakes to the camping box&amp;rdquo; and turn it into something the application can act on.&lt;/p&gt;&#10;&lt;p&gt;I created an agentic fine-tuning and evaluation loop with a custom evaluation harness: an AI agent proposes one change at a time, a custom evaluation harness measures it against a frozen test set, and every run gets appended to a log with its settings, result and next decision. The agent does the all the work of dataset generation, training, evaluation and diagnosis.&lt;/p&gt;&#10;&lt;h2 id="setup"&gt;Setup&#10;&lt;/h2&gt;&lt;p&gt;I built a fixed test set of 45 requests covering nine intents, five cases each, and froze it. Every model, change and training run was measured against those same 45 cases. The output metrics of each run were: intent accuracy, meaning did it pick the right action, and parameter accuracy, meaning did it extract the right details. A third important number is: fallbacks, the count of cases where the model declined to answer and returned &amp;ldquo;unclear.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The OpenAI baseline (gpt-5.6-luna) was created and frozen. It scored 93.33% intent and 71.11% parameter accuracy with no fallbacks.&lt;/p&gt;&#10;&lt;p&gt;All local training ran on a consumer RTX 3060, in &lt;a class="link" href="https://learn.microsoft.com/en-us/windows/wsl/about" target="_blank" rel="noopener"&#10; &gt;WSL2&lt;/a&gt; with &lt;a class="link" href="https://docs.nvidia.com/cuda/wsl-user-guide/" target="_blank" rel="noopener"&#10; &gt;CUDA&lt;/a&gt;. &lt;a class="link" href="https://arxiv.org/abs/2106.09685" target="_blank" rel="noopener"&#10; &gt;LoRA&lt;/a&gt; &lt;a class="link" href="https://www.cactuscompute.com/blog/finetuning-needle" target="_blank" rel="noopener"&#10; &gt;fine-tuning&lt;/a&gt;, rank 16, alpha 32, learning rate 1e-4, batch size 4. A 15-epoch run takes about 20 minutes.&lt;/p&gt;&#10;&lt;p&gt;The entire source, including training data, eval harness, scripts and agent skill is on GitHub - &lt;a class="link" href="https://github.com/ashtewari/needle-tuner" target="_blank" rel="noopener"&#10; &gt;needle-tuner&lt;/a&gt;.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/needle-finetune-gpu-usage.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="part-1---needle2"&gt;Part 1 - needle2&#10;&lt;/h2&gt;&lt;p&gt;The first local comparison with &lt;a class="link" href="https://cactuscompute.com/blog/needle-2" target="_blank" rel="noopener"&#10; &gt;needle2&lt;/a&gt; model from &lt;a class="link" href="https://cactuscompute.com/needle" target="_blank" rel="noopener"&#10; &gt;Cactus&lt;/a&gt;. It scored 4.44% intent accuracy. The problem turned out to be how I was asking the question. I had given the model one abstract, generic &amp;ldquo;classify this&amp;rdquo; tool. I replaced it with eight concrete tools, one for each real action the application supports.&lt;/p&gt;&#10;&lt;p&gt;The result went to 62.22% intent and 68.89% parameter accuracy.&lt;/p&gt;&#10;&lt;p&gt;With this reasonable starting point, I &lt;a class="link" href="https://www.cactuscompute.com/blog/finetuning-needle" target="_blank" rel="noopener"&#10; &gt;fine-tuned&lt;/a&gt; on a small purpose-built training set and ran a structured loop: change one thing, measure against the frozen test set, write down the result.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Needle2 configuration&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Intent&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Base model, redesigned tools, no training&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;62.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;68.89%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Best fine-tuned run (5 epochs, rank 8)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;64.44%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;62.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Longer training (30 epochs)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;57.78%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;64.44%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Heavier defaults&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;42.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;51.11%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The best training result gained two points on intent accuracy over no training at all, and lost six on parameters. More training made things worse. The heaviest run also refused to answer far more often, with 15 fallbacks against 9 for the untrained model. The results were noisy enough to make any single comparison suspect. Two runs of the identical best configuration measured 64.44% and 60.00%, so I stopped treating one good run as evidence of anything. Validation loss kept improving while held-out accuracy got worse. Had I watched the training dashboard instead of the frozen test set, I would have shipped a regression. I stopped at this point.&lt;/p&gt;&#10;&lt;h2 id="part-2---needle3"&gt;Part 2 - needle3&#10;&lt;/h2&gt;&lt;p&gt;A new version of the model family had shipped in the mean time, with a different architecture and a different native engine. Untrained, &lt;a class="link" href="https://cactuscompute.com/needle" target="_blank" rel="noopener"&#10; &gt;needle3&lt;/a&gt; measured 71.11% intent and 66.67% parameter accuracy with 7 fallbacks. I modified the agentic loop to support the new model and executed again.&lt;/p&gt;&#10;&lt;p&gt;A one-epoch smoke test scored 68.89% and 73.33%, which is one run on one epoch and proves nothing on its own. A full 30-epoch run at the wrapper defaults scored 71.11% and 66.67%, identical to the untrained base. However, the per-intent breakdown had shifted underneath, some intents up and some down, while the totals stayed flat. Validation loss bottomed out at epoch 13 and climbed for the remaining 17 epochs while training loss kept falling. Same &lt;a class="link" href="https://developers.google.com/machine-learning/crash-course/overfitting" target="_blank" rel="noopener"&#10; &gt;overfitting&lt;/a&gt; pattern &lt;a class="link" href="https://cactuscompute.com/blog/needle-2" target="_blank" rel="noopener"&#10; &gt;needle2&lt;/a&gt; had shown.&lt;/p&gt;&#10;&lt;h2 id="the-issue"&gt;The issue&#10;&lt;/h2&gt;&lt;p&gt;Instead of tuning another hyperparameter, I stopped reading aggregate scores and started reading individual failures.&lt;/p&gt;&#10;&lt;p&gt;The training data and the held-out test set agreed on which intent was correct. They disagreed on the convention for writing the parameters down.&lt;/p&gt;&#10;&lt;p&gt;The first problem was descriptive adjectives. For delete requests, the test set strips condition words from item names, so &amp;ldquo;the broken toaster&amp;rdquo; is expected to yield &amp;rsquo;toaster&amp;rsquo;. All 12 authored delete rows in my training data did the opposite, keeping &amp;lsquo;cracked mixing bowl&amp;rsquo; and &amp;rsquo;expired pain reliever&amp;rsquo; intact.&lt;/p&gt;&#10;&lt;p&gt;The second problem was a wrong key name. For inventory-viewing requests, the training data used a &amp;rsquo;location&amp;rsquo; key for the container slot while every other intent, and the test set, used &amp;lsquo;boxLabel&amp;rsquo;. It also carried a &amp;lsquo;scope&amp;rsquo; key that appears in zero held-out cases.&lt;/p&gt;&#10;&lt;p&gt;The model was learning my convention faithfully and being marked wrong for it, on 100% of those cases. That also explains why longer training hurt: more epochs meant learning the wrong answer more thoroughly.&lt;/p&gt;&#10;&lt;h2 id="the-fix"&gt;The Fix&#10;&lt;/h2&gt;&lt;p&gt;I corrected 12 delete rows and 20 view-inventory rows, regenerated the derived datasets, re-pinned the integrity hashes and re-ran the validation suite. No hyperparameter change and dropped Epoch count from 30 to 15, taken from the previous run&amp;rsquo;s validation-loss minimum.&lt;/p&gt;&#10;&lt;p&gt;Result: 82.22% intent and 75.56% parameter accuracy, 6 fallbacks.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Run&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Intent&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fallbacks&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Needle2 base&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;62.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;68.89%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Needle2 best fine-tune&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;64.44%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;62.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;not recorded&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Needle3 base&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;71.11%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;66.67%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Needle3 fine-tuned, original data, 30 epochs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;71.11%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;66.67%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Needle3 fine-tuned, corrected data, 15 epochs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;82.22%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;75.56%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenAI (frozen baseline)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;93.33%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;71.11%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;That is 11 points of intent accuracy over its own base model and 20 over &lt;a class="link" href="https://cactuscompute.com/blog/needle-2" target="_blank" rel="noopener"&#10; &gt;needle2&lt;/a&gt;&amp;rsquo;s base. It is also the first time a local model beat the hosted API on parameter accuracy, 75.56% against 71.11%. Intent accuracy is still 11 points behind.&lt;/p&gt;&#10;&lt;p&gt;The per-intent movement matches the diagnosis. Delete went from 80%/40% to 100%/80%. Search went from 60%/60% to 100%/100%. Update improved as well, since the bare-noun convention is shared by every intent that names an item.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://developers.google.com/machine-learning/crash-course/overfitting" target="_blank" rel="noopener"&#10; &gt;Validation loss&lt;/a&gt; behaved differently too. With the corrected data it declined every one of the 15 epochs, with none of the mid-run turnaround the unfixed run showed. The contradictory labels had been generating the &lt;a class="link" href="https://developers.google.com/machine-learning/crash-course/overfitting" target="_blank" rel="noopener"&#10; &gt;overfitting&lt;/a&gt; signal themselves.&lt;/p&gt;&#10;&lt;h2 id="next"&gt;Next&#10;&lt;/h2&gt;&lt;p&gt;Parameter extraction already beats the API. Intent classification is 11 points behind, and most of the remaining gap sits in one diagnosed failure mode: deciding whether a vague request is actionable.&lt;/p&gt;&#10;&lt;p&gt;The next lever is contrastive training examples sitting right on that boundary. After that the options get expensive: changes to how the model produces its answers, training that explicitly teaches it what a wrong answer looks like, or a different architecture.&lt;/p&gt;&#10;&lt;p&gt;Hyperparameter tuning and the size of the training dataset should also be revisited to explore further improvements to intent classification.&lt;/p&gt;&#10;&lt;p&gt;Because the same 45 cases were used to decide which experiment to run next, they are now a &lt;a class="link" href="https://developers.google.com/machine-learning/crash-course/overfitting/dividing-datasets" target="_blank" rel="noopener"&#10; &gt;model-selection set rather than an unbiased final exam&lt;/a&gt;. Before anyone calls this production-ready it has to be tested against a separate set of cases it has never influenced.&lt;/p&gt;&#10;&lt;p&gt;The project also produced a reproducible pipeline along the way. A frozen test set, hash-pinned datasets, a documented training process, a log of every attempt including the failures, and a default path that validates without training and without calling paid APIs. That infrastructure is the reason the annotation issue was discoverable at all.&lt;/p&gt;&#10;</description></item><item><title>Giving OpenClaw Secure Access to Cloud Services Without Sharing Your Password</title><link>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</link><pubDate>Mon, 06 Apr 2026 04:48:17 +0000</pubDate><guid>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</guid><description>&lt;p&gt;Device Code Flow has been built into OAuth2 for years, originally designed for TVs and game consoles. It works just as well for a Docker container. It requires no credentials stored on the agent machine. It gives you narrow, revocable access that the agent cannot exceed.&lt;/p&gt;&#10;&lt;p&gt;I have OpenClaw deployed in a sandboxed docker container on a dedicated host. I communicate with it via a Telegram bot, in a locked-down chat.&lt;/p&gt;&#10;&lt;p&gt;I wired the device-code authentication pattern up with OpenClaw and Microsoft (Personal/Consumer) services, but the authentication approach works with any app and any command-line tool you want to run in a headless environment, including Custom APIs. Device code flow + Entra ID app registrations give you a zero-stored-credentials gateway to any HTTP API you can build, not just Microsoft Graph.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/openclaw-telegram-integration.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;strong&gt;What Is Device Code Flow?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;OAuth2 device code flow (RFC 8628) was designed for &amp;ldquo;input-constrained devices&amp;rdquo; — things without a keyboard or a browser. Think of how you sign in to Netflix on a smart TV: a short code appears on screen, you visit a URL on your phone, you type the code in, and the TV logs in. You never type your password on the TV.&lt;/p&gt;&#10;&lt;p&gt;The official name for that pattern is the device authorization grant. A Docker container is, from the protocol&amp;rsquo;s perspective, exactly the same kind of device. It has no browser. It cannot perform an interactive redirect. But it can make HTTP requests, and that is all it needs.&lt;/p&gt;&#10;&lt;p&gt;The flow has three steps:&lt;/p&gt;&#10;&lt;p&gt;1. The app posts to the identity provider&amp;rsquo;s device code endpoint. It gets back a short user code (like &amp;ldquo;WDJB-MJHT&amp;rdquo;), a verification URL, and a polling device code.&lt;/p&gt;&#10;&lt;p&gt;2. The user visits the URL on any browser, on any device — their phone, laptop, anything — and enters the short code. They sign in normally, with their usual credentials and MFA if it is enabled.&lt;/p&gt;&#10;&lt;p&gt;3. The app polls the token endpoint in the background. Once the user finishes signing in, the poll returns a real access token and a refresh token. The app stores these and uses them for API calls going forward.&lt;/p&gt;&#10;&lt;p&gt;The app never sees the password. The password goes directly from the user&amp;rsquo;s browser to the identity provider. The app only ever handles two things: a short temporary code that it sends to the user, and a token that the identity provider gives back once the user has authenticated.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw running in a Docker container fits this model exactly.&lt;/p&gt;&#10;&lt;h2 id="which-services-support-this"&gt;Which Services Support this?&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is not a Microsoft-only feature. It is a standard OAuth2 extension (RFC 8628) and most major identity providers support it — including Microsoft, Google, GitHub, and AWS. If a service uses Okta or Auth0 for identity, those support it too.&lt;/p&gt;&#10;&lt;h2 id="the-security-architecture"&gt;The Security Architecture&#10;&lt;/h2&gt;&lt;p&gt;The agent never sees your credentials. Your password is typed in your browser, on your device, to your identity provider&amp;rsquo;s servers. It never touches the container. The agent only handles a short temporary code to give you, and a token issued by the provider once you have authenticated.&lt;/p&gt;&#10;&lt;p&gt;The device code is one-time and short-lived. After the user authenticates, the code is permanently invalidated. An intercepted code is useless without the user&amp;rsquo;s credentials and MFA.&lt;/p&gt;&#10;&lt;p&gt;Scopes define the ceiling. You configure the OAuth app or app registration to request only specific permissions. The agent cannot exceed those scopes. If you configure read-only access to email, the token cannot be used to send or delete email.&lt;/p&gt;&#10;&lt;p&gt;This is the authentication pattern that many of the consumer devices you already own use to access your accounts on your behalf. Your TV&amp;rsquo;s YouTube app, your smart home hub&amp;rsquo;s Google integration, the GitHub CLI you use on your workstation — these all use device flow. You have been trusting it for years without knowing what it was called.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/device-code-flow-workflow.drawio.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="how-the-implementation-works"&gt;How the implementation works&#10;&lt;/h2&gt;&lt;p&gt;There are three components, and they run as separate Docker services that talk to each other through the Docker socket.&lt;/p&gt;&#10;&lt;p&gt;The app sidecar is your application container. It runs your CLI tool and your auth logic. It does nothing on its own. Its only job is to hold the authenticated token cache and execute commands on demand when OpenClaw calls into it.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw is the AI agent container. It connects to Telegram, understands natural language, and knows which skills to run for which requests. It does not know anything about OAuth or your specific app. It just runs shell commands you specified and reports results back to you.&lt;/p&gt;&#10;&lt;p&gt;The Telegram auth bridge is a small script that lives inside the sidecar. It registers a device code callback, requests an auth flow, and uses the Telegram Bot API to forward the code to you — then confirms when authentication completes.&lt;/p&gt;&#10;&lt;p&gt;The compose file looks roughly like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;myapp-sidecar&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/youruser/myapp:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;XDG_DATA_HOME=/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${APP_DATA_DIR:-./app-data}:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;openclaw-gateway&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;context&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dockerfile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Dockerfile.openclaw&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./skills:/app/skills&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;group_add&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${DOCKER_GID:-999}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;18789:18789&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-callback-pattern"&gt;The Callback Pattern&#10;&lt;/h2&gt;&lt;p&gt;The auth manager has a method called set_device_code_callback. You pass it a function, and when the device code is ready, the auth manager calls your function with the code and the verification URL rather than trying to open a browser.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AuthManager&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self, client_id: str, authority: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client_id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority &lt;span style="color:#f92672"&gt;=&lt;/span&gt; authority&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_tokens &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_load_cache()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set_device_code_callback&lt;/span&gt;(self, fn) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fn&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_device_code_auth&lt;/span&gt;(self) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/devicecode&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id, &lt;span style="color:#e6db74"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: OAUTH_SCOPES},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; flow &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; verification_uri &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;interval&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;expires_in&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;: user_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;: verification_uri,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; webbrowser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(verification_uri)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deadline &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; expires_in&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; deadline:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(interval)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/token&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;urn:ietf:params:oauth:grant-type:device_code&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;: device_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;status_code &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; &lt;span style="color:#f92672"&gt;and&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;access_token&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; body:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_store_tokens(body)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error &lt;span style="color:#f92672"&gt;=&lt;/span&gt; body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;authorization_pending&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;slow_down&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;in&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;authorization_declined&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;expired_token&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(error)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error_description&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Auth failed&amp;#34;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The polling loop handles the three error codes the spec defines: authorization_pending (keep waiting), slow_down (back off and increase the interval by 5 seconds), and the terminal errors that stop polling.&lt;/p&gt;&#10;&lt;h2 id="the-telegram-bridge"&gt;The Telegram Bridge&#10;&lt;/h2&gt;&lt;p&gt;With the callback mechanism in place, the Telegram bridge is just a script that registers a callback and uses the Telegram Bot API to deliver the code to you:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;,&lt;/span&gt; os&lt;span style="color:#f92672"&gt;,&lt;/span&gt; sys&lt;span style="color:#f92672"&gt;,&lt;/span&gt; traceback&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; httpx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.auth &lt;span style="color:#f92672"&gt;import&lt;/span&gt; AuthManager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.config &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Settings&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_BOT_TOKEN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_BOT_TOKEN&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_CHAT_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_CHAT_ID&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;send_telegram&lt;/span&gt;(text: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(url, json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: text,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; int:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; settings &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Settings()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AuthManager(settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;client_id, settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;authority)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;is_signed_in():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;&amp;#34;Already authenticated.&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_device_code&lt;/span&gt;(info: dict) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;*Auth Required*&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Go to: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;verification_uri&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Enter code: `&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;user_code&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Complete sign-in in your browser and I&amp;#39;ll confirm when done.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_event_loop()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create_task(send_telegram(msg))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RuntimeError&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;import&lt;/span&gt; requests&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID, &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: msg, &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_device_code_callback(on_device_code)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_info &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sign_in()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Signed in* as &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; exc:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Auth failed:* &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;exc&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;traceback&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format_exc()[:&lt;span style="color:#ae81ff"&gt;800&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sys&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exit(asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(main()))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-openclaw-skill"&gt;The OpenClaw Skill&#10;&lt;/h2&gt;&lt;p&gt;On the OpenClaw side, a &amp;ldquo;skill&amp;rdquo; is a markdown file. When OpenClaw sees a trigger phrase in Telegram, it runs the associated shell command. The markdown file looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;myapp-auth&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Authenticate with the cloud service via device code flow, coordinated through Telegram.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## myapp-auth&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Use this skill when the user sends &amp;#34;/auth&amp;#34;, &amp;#34;authenticate&amp;#34;, or &amp;#34;sign in&amp;#34;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Run the following command and wait up to 360 seconds for it to complete.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The script sends the device code to the user via Telegram and confirms when done.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker exec myapp-sidecar-1 python /app/scripts/telegram_auth.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is a mature, widely-supported OAuth2 pattern that maps naturally onto OpenClaw running in Docker container. With this pattern OpenClaw never handles your credentials. It gets scoped, revocable tokens from the identity provider, after you have authenticated in your own browser. A Telegram bot is all you need to coordinate the handoff of the temporary code.&lt;/p&gt;&#10;&lt;p&gt;The approach works for many use-cases across Microsoft accounts, Google, GitHub, AWS, Auth0, Okta and others. It supports exactly the kinds of personal automations that make an OpenClaw genuinely useful in daily life.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;References&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/" target="_blank" rel="noopener"&#10; &gt;OpenClaw Documentation&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/tools/creating-skills" target="_blank" rel="noopener"&#10; &gt;OpenClaw: Creating Skills&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8628" target="_blank" rel="noopener"&#10; &gt;RFC 8628: OAuth 2.0 Device Authorization Grant&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/identity-platform/scenario-desktop-acquire-token-device-code-flow" target="_blank" rel="noopener"&#10; &gt;Microsoft Identity Platform: Acquire a Token Using Device Code Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/graph/permissions-reference" target="_blank" rel="noopener"&#10; &gt;Microsoft Graph Permissions Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" target="_blank" rel="noopener"&#10; &gt;Auth0: Device Authorization Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://developer.okta.com/docs/guides/device-authorization-grant/main/" target="_blank" rel="noopener"&#10; &gt;Okta: Configure Device Authorization Grant Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://core.telegram.org/bots/api" target="_blank" rel="noopener"&#10; &gt;Telegram Bot API&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.docker.com/reference/compose-file/" target="_blank" rel="noopener"&#10; &gt;Docker Compose File Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Understanding RAG @ All Things Open AI 2025</title><link>https://www.tewari.info/2025/03/30/understanding-rag-all-things-open-ai-2025/</link><pubDate>Mon, 31 Mar 2025 02:29:39 +0000</pubDate><guid>https://www.tewari.info/2025/03/30/understanding-rag-all-things-open-ai-2025/</guid><description>&lt;p&gt;&lt;strong&gt;What I learned while creating Bookshelf (open source RAG Application)&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;I had the opportunity to speak at the &lt;a class="link" href="https://allthingsopen.ai/" target="_blank" rel="noopener"&#10; &gt;AllThingsOpen.ai 2025 Conference&lt;/a&gt; recently. It was a wonderful experience to learn from and share with so many amazing fellow presenters and attendees. All Things Open AI 2025 session recordings are available now.&lt;/p&gt;&#10;&lt;p&gt;I spoke about several key aspects of Retrieval-Augmented Generation (RAG) and vector databases. The main topics I addressed included The Core Concepts of RAG, Embedding Models vs. Inference Models, Handling Multiple Embeddings and various Optimizations Beyond Naive RAG such as chunking strategies, enrichment with metadata, auto-merging retriever, and reranking models. Finally, I touched on Local Execution and GPU Utilization.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/ato-ai-2025-bookshelf-rag-ash-slide01.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can watch the recording here: &lt;a class="link" href="https://youtu.be/vWrG8whSHwk?si=CJg0qlb5FwTU9w7y" target="_blank" rel="noopener"&#10; &gt;YouTube - How I created Bookshelf: An Open Source AI-Powered Personal Knowledge Base - Ash Tewari&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;The slides can be downloaded from here: (&lt;a class="link" href="https://drive.google.com/drive/folders/1SSceCPGBytJ7B4nP2kQyX_DPpP6iHenH?usp=sharing" target="_blank" rel="noopener"&#10; &gt;Google Drive Link&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;References and Links:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;AI Powered Bookshelf - &lt;a class="link" href="https://www.tewari.info/2024/04/28/ai-powered-bookshelf/" &gt;Blog Post&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;HuggingFace - &lt;a class="link" href="https://huggingface.co/spaces/mteb/leaderboard" target="_blank" rel="noopener"&#10; &gt;MTEB Leaderboard&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;PyTorch - &lt;a class="link" href="https://pytorch.org/get-started/locally/" target="_blank" rel="noopener"&#10; &gt;Local Execution&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://ai.plainenglish.io/a-brief-introduction-to-retrieval-augmented-generation-rag-b7eb70982891" target="_blank" rel="noopener"&#10; &gt;Medium - A Brief Introduction to Retrieval Augmented Generation(RAG)&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;[Microsoft - RAG Architecture](http://Design and develop a RAG solution)&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.aws.amazon.com/sagemaker/latest/dg/jumpstart-foundation-models-customize-rag.html" target="_blank" rel="noopener"&#10; &gt;Amazon - Retrieval Augmented Generation&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>SPCHR: Speech To Text App for Windows</title><link>https://www.tewari.info/2025/02/09/spchr-speech-to-text-app-for-windows/</link><pubDate>Mon, 10 Feb 2025 04:32:25 +0000</pubDate><guid>https://www.tewari.info/2025/02/09/spchr-speech-to-text-app-for-windows/</guid><description>&lt;p&gt;I&amp;rsquo;m excited to announce the release of SPCHR. Pronounced as &amp;ldquo;speaker&amp;rdquo;. I know, I know. My apologies, it was too late at night and that&amp;rsquo;s the best I could come up with ;)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;It is a Windows desktop application for speech-to-text transcription.&lt;/li&gt;&#10;&lt;li&gt;You can use it to &amp;ldquo;add&amp;rdquo; voice input to applications that don’t natively support it. It works anywhere you can type or paste text.&lt;/li&gt;&#10;&lt;li&gt;You can use it entirely locally on your PC if you like, so it is private and secure.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="key-features"&gt;Key Features&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Flexible&lt;/strong&gt;: SPCHR can use either Azure Speech Services or a local OpenAI Whisper model, giving you the flexibility to choose between cloud-based and local transcription.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Zero Configuration&lt;/strong&gt;: The application works immediately with local Whisper processing - no account setup required!&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Global Hotkey&lt;/strong&gt;: Start and stop recording from &lt;em&gt;any&lt;/em&gt; application with Ctrl+Alt+L.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Seamless Integration&lt;/strong&gt;: Transcribed text is automatically pasted into your active window.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="getting-started"&gt;Getting Started&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;Clone the repository&lt;/li&gt;&#10;&lt;li&gt;Build the solution using Visual Studio and run it&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Press Ctrl+Alt+L and start speaking&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Watch as your words appear in your active window&lt;/li&gt;&#10;&lt;li&gt;For those wanting cloud-based transcription, simply add your Azure Speech Services credentials to the configuration file.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="open-source"&gt;Open Source&#10;&lt;/h2&gt;&lt;p&gt;SPCHR is released under the MIT License, and I welcome contributions from the community. Whether you&amp;rsquo;re interested in adding features, fixing bugs, or improving documentation, your help is appreciated.&lt;/p&gt;&#10;&lt;p&gt;Check out &lt;a class="link" href="https://github.com/ashtewari/spchr" target="_blank" rel="noopener"&#10; &gt;GitHub repository&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&#10;&lt;/h2&gt;&lt;p&gt;This is just the beginning for SPCHR. Several improvements can be done:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Additional language support&lt;/li&gt;&#10;&lt;li&gt;Customizable hotkeys&lt;/li&gt;&#10;&lt;li&gt;Installer&lt;/li&gt;&#10;&lt;li&gt;UI Enhancements&lt;/li&gt;&#10;&lt;li&gt;AI Enhanced Features&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="try-it-out"&gt;Try it out&#10;&lt;/h2&gt;&lt;p&gt;Ready to transform how you interact with your computer? Visit the &lt;a class="link" href="https://github.com/ashtewari/spchr" target="_blank" rel="noopener"&#10; &gt;GitHub repository&lt;/a&gt; to get started with SPCHR today.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m excited to see how you&amp;rsquo;ll use SPCHR in your workflow and look forward to your feedback!&lt;/p&gt;&#10;</description></item><item><title>WhichBox - AI Assistant App</title><link>https://www.tewari.info/2024/08/04/whichbox-ai-assistant-app/</link><pubDate>Sun, 04 Aug 2024 17:37:56 +0000</pubDate><guid>https://www.tewari.info/2024/08/04/whichbox-ai-assistant-app/</guid><description>&lt;p&gt;Do you remember which one of those boxes in the garage has your old phone? Or that toy your child wants to play with again? Or the tripod you need for your weekend trip? Oh what about that massager that you could really use right now! You would probably have to dig through those boxes to find it. Same problem when moving. There are always some boxes that you don&amp;rsquo;t want to open right away, but you wish you could open just the right one when you need something.&lt;/p&gt;&#10;&lt;p&gt;I have created &lt;a class="link" href="https://whichbox.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;WhichBox&lt;/a&gt;- your AI Assistant to find that box. It uses latest Vision AI models to help you find things quickly. Here&amp;rsquo;s what you do -&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Take pictures of the content inside each labelled box to create an inventory.&lt;/li&gt;&#10;&lt;li&gt;Use WhichBox to easily identify the box containing the item you are looking for.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;You can check it out here - &lt;a class="link" href="https://whichbox.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://whichbox.streamlit.app/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;The demo has four photos of labelled boxes with some content in them. Note that there can be multiple photos of the same box. You can take photos as you are filling up the box to capture things at the bottom.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-47.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can ask for a specific thing, like &amp;ldquo;Find Fitbit&amp;rdquo; or just &amp;ldquo;fitbit&amp;rdquo;&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-48.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can go for a general category, like &amp;ldquo;Camera Equipment&amp;rdquo;&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-49.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can get all boxes containing &amp;ldquo;USB Adapters&amp;rdquo;&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-50.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can look for &amp;ldquo;Groot&amp;rdquo; or if you can&amp;rsquo;t remember the name of a specific toy then you can look for &amp;ldquo;all toys of movie characters&amp;rdquo;&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-51.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;For now, you can bring your own API Key and use your own photos to try this out.&lt;/p&gt;&#10;&lt;p&gt;WhichBox : &lt;a class="link" href="https://whichbox.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://whichbox.streamlit.app/&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>AI Powered Bookshelf</title><link>https://www.tewari.info/2024/04/28/ai-powered-bookshelf/</link><pubDate>Mon, 29 Apr 2024 00:25:55 +0000</pubDate><guid>https://www.tewari.info/2024/04/28/ai-powered-bookshelf/</guid><description>&lt;p&gt;Bookshelf is a Generative AI application built as a rudimentary, but fairly capable, &lt;a class="link" href="https://docs.llamaindex.ai/en/stable/use_cases/q_and_a/" target="_blank" rel="noopener"&#10; &gt;RAG&lt;/a&gt; implementation written in &lt;a class="link" href="https://www.python.org/doc/" target="_blank" rel="noopener"&#10; &gt;python&lt;/a&gt;. It can use an open source LLM model (running locally or in the cloud) or a GPT model via OpenAI&amp;rsquo;s API.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The application is created using &lt;a class="link" href="https://streamlit.io/" target="_blank" rel="noopener"&#10; &gt;streamlit&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;I used &lt;a class="link" href="https://www.llamaindex.ai/" target="_blank" rel="noopener"&#10; &gt;llama-index&lt;/a&gt; for orchestrating the loading of documents into the &lt;a class="link" href="https://en.wikipedia.org/wiki/Vector_database" target="_blank" rel="noopener"&#10; &gt;vector database&lt;/a&gt;. Only TokenTextSplitter is currently used. It does not optimize for PDF, html and other formats.&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.trychroma.com/" target="_blank" rel="noopener"&#10; &gt;ChromaDb&lt;/a&gt; is the vector database to store the embedding vectors and metadata of the document nodes.&lt;/li&gt;&#10;&lt;li&gt;You can use any open source embeddings model from &lt;a class="link" href="https://huggingface.co/models?other=embeddings" target="_blank" rel="noopener"&#10; &gt;HuggingFace&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Bookshelf will automatically use the GPU when creating local embeddings, if the GPU is available on your machine.&lt;/li&gt;&#10;&lt;li&gt;You can use &lt;a class="link" href="https://platform.openai.com/docs/guides/embeddings" target="_blank" rel="noopener"&#10; &gt;OpenAI embeddings&lt;/a&gt; as well. There is no way to use a specific OpenAI embedding model or configure the parameters yet.&lt;/li&gt;&#10;&lt;li&gt;Use OpenAI API or any OpenAI compatible LLM API (using &lt;a class="link" href="https://lmstudio.ai/" target="_blank" rel="noopener"&#10; &gt;LMStudio&lt;/a&gt;, &lt;a class="link" href="https://ollama.com/" target="_blank" rel="noopener"&#10; &gt;Ollama&lt;/a&gt; or &lt;a class="link" href="https://github.com/oobabooga/text-generation-webui" target="_blank" rel="noopener"&#10; &gt;text-generation-webui&lt;/a&gt;) of your choice.&lt;/li&gt;&#10;&lt;li&gt;There is a live demo on streamlit cloud - &lt;a class="link" href="https://bookshelf.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://bookshelf.streamlit.app/&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;The demo allows only OpenAI integration. You can run it locally for accessing Open Source embedding models and LLMs.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;Live demo - &lt;a class="link" href="https://bookshelf.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://bookshelf.streamlit.app/&lt;/a&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;You will need your OpenAI api key for the demo.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-39.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;If you are running it locally, you will have the option of using an Open Source LLM instance via an API Url. In the screenshot, I am using an open source Embedding Model from HuggingFace (sentence-transformers/all-mpnet-base-v2) and The local LLM server at http://localhost:1234/v1&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-40.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Collections tab shows all collections in the database. It also shows the names of all the files in the selected collection. You can inspect individual chunks for the metadata and text of each chunk. You can delete all contents of the collection &lt;strong&gt;(&lt;/strong&gt; &lt;strong&gt;there is no warning&lt;/strong&gt; &lt;strong&gt;)&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-38.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can modify the collection name to create a new collection. Multiple files can be uploaded at the same time. You can specify if you want to extract metadata from the file contents. &lt;strong&gt;Enabling this option can add significant cost&lt;/strong&gt; because it employs Extractors which use LLM to generate title, summaries, keywords and questions for each document.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-41.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;On the Retrieve tab, you can query chunks which are semantically related to your query.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-43.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;On the Prompt tab, you can prompt your LLM. The context as well as the Prompt Template is editable.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-44.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Here is an example of using the context retrieved from chunks in the Vector database to query the LLM.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-45.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;This inference was performed using &lt;a class="link" href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-gguf" target="_blank" rel="noopener"&#10; &gt;Phi3&lt;/a&gt; model running locally on LMStudio.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-46.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Code is on Github - &lt;a class="link" href="https://github.com/ashtewari/bookshelf" target="_blank" rel="noopener"&#10; &gt;https://github.com/ashtewari/bookshelf&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Have fun!&lt;/p&gt;&#10;</description></item><item><title>Prompt Engineering and Security - Custom GPT</title><link>https://www.tewari.info/2024/01/21/prompt-engineering-and-security-custom-gpt/</link><pubDate>Sun, 21 Jan 2024 23:01:24 +0000</pubDate><guid>https://www.tewari.info/2024/01/21/prompt-engineering-and-security-custom-gpt/</guid><description>&lt;p&gt;I have been delving into Advanced Prompt Engineering and Security techniques for Large Language Models (LLMs). As an exercise, I have created a custom GPT in &lt;a class="link" href="https://openai.com/blog/introducing-gpts" target="_blank" rel="noopener"&#10; &gt;ChatGPT&lt;/a&gt; to help practice spelling of English words. &lt;a class="link" href="https://bit.ly/3Ua0oib" target="_blank" rel="noopener"&#10; &gt;Spell It&lt;/a&gt; GPT is secured with Advanced Prompt Engineering techniques to guard against common attack vectors, including Direct Prompt Injection, Prompt Leaking, Role Playing, Simulation, DAN and Code Injection.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/spell-it-gpt-256.png"&#10;&#9;&#9;&#9;alt="Spell-It-GPT"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Spell-It-GPT&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Play with the &lt;a class="link" href="https://bit.ly/3Ua0oib" target="_blank" rel="noopener"&#10; &gt;Spell It&lt;/a&gt; GPT and try to break it. It is not impossible but (probably) fairly difficult to do ;) Regardless, practice spelling and have fun!&lt;/p&gt;&#10;&lt;p&gt;Switch to &lt;a class="link" href="https://openai.com/blog/chatgpt-can-now-see-hear-and-speak" target="_blank" rel="noopener"&#10; &gt;Voice Mode&lt;/a&gt; in ChatGPT Mobile App to practice spelling and put headsets on for best results!&lt;/p&gt;&#10;&lt;p&gt;Spell It: &lt;a class="link" href="https://bit.ly/3Ua0oib" target="_blank" rel="noopener"&#10; &gt;https://bit.ly/3Ua0oib&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Here are some resources you can learn more about Prompt Engineering and Security:&lt;br&gt;&#10;&lt;a class="link" href="https://www.mercity.ai/blog-post/advanced-prompt-engineering-techniques" target="_blank" rel="noopener"&#10; &gt;Advanced Prompt Engineering Techniques&lt;/a&gt;&lt;br&gt;&#10;&lt;a class="link" href="https://owasp.org/www-project-top-10-for-large-language-model-applications/" target="_blank" rel="noopener"&#10; &gt;OWASP Top 10 for Large Language Model Applications&lt;/a&gt;&lt;br&gt;&#10;&lt;a class="link" href="https://github.com/OWASP/www-project-top-10-for-large-language-model-applications/wiki/Educational-Resources" target="_blank" rel="noopener"&#10; &gt;Educational Resources&lt;/a&gt;&lt;br&gt;&#10;&lt;a class="link" href="https://www.promptingguide.ai/risks/adversarial" target="_blank" rel="noopener"&#10; &gt;Adversarial Prompting in LLMs&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>KHEL Coin on Ethereum</title><link>https://www.tewari.info/2022/05/09/khel-coin-on-ethereum/</link><pubDate>Tue, 10 May 2022 01:41:59 +0000</pubDate><guid>https://www.tewari.info/2022/05/09/khel-coin-on-ethereum/</guid><description>&lt;p&gt;KhelCoin (KHEL) is an &lt;a class="link" href="https://ethereum.org/en/developers/docs/standards/tokens/erc-20/" target="_blank" rel="noopener"&#10; &gt;ERC-20 token&lt;/a&gt; available on &lt;a class="link" href="https://ethereum.org/en/" target="_blank" rel="noopener"&#10; &gt;Ethereum&lt;/a&gt; blockchain. It is a &lt;a class="link" href="https://ethereum.org/en/smart-contracts/" target="_blank" rel="noopener"&#10; &gt;Smart Contract&lt;/a&gt; created with &lt;a class="link" href="https://docs.soliditylang.org/en/latest/" target="_blank" rel="noopener"&#10; &gt;Solidity&lt;/a&gt;, &lt;a class="link" href="https://www.openzeppelin.com/contracts" target="_blank" rel="noopener"&#10; &gt;OpenZeppelin&lt;/a&gt;, &lt;a class="link" href="https://trufflesuite.com/" target="_blank" rel="noopener"&#10; &gt;Truffle&lt;/a&gt;, &lt;a class="link" href="https://trufflesuite.com/ganache/" target="_blank" rel="noopener"&#10; &gt;Ganache&lt;/a&gt;, &lt;a class="link" href="https://remix.ethereum.org/" target="_blank" rel="noopener"&#10; &gt;Remix&lt;/a&gt;, &lt;a class="link" href="https://web3js.readthedocs.io/" target="_blank" rel="noopener"&#10; &gt;web3.js&lt;/a&gt; and &lt;a class="link" href="https://code.visualstudio.com/" target="_blank" rel="noopener"&#10; &gt;VSCode&lt;/a&gt;. It is currently available on &lt;a class="link" href="https://rinkeby.etherscan.io/token/0x15AFeB0A2caD2caC3A8140c342789C310Ad82A83" target="_blank" rel="noopener"&#10; &gt;Rinkeby&lt;/a&gt; test network. It will be launched on the Mainnet in the near future.&lt;/p&gt;&#10;&lt;p&gt;You can get KHEL coins from this website - &lt;a class="link" href="https://ashtewari.github.io/khelcoin" target="_blank" rel="noopener"&#10; &gt;KHEL Coin ICO&lt;/a&gt;&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/khelcoin-ico-metamask.png"&#10;&#9;&#9;&#9;alt="KhelCoin ICO page prompting to install Metamask"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Head over to the ICO website. You will be prompted to install &lt;a class="link" href="https://metamask.io/" target="_blank" rel="noopener"&#10; &gt;Metamask&lt;/a&gt; and connect to your account on Rinkeby testnet. Click on &amp;ldquo;Buy KHEL Coins&amp;rdquo; button to get your own coins!&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/khelcoin-ico-buy.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Source code is on github - &lt;a class="link" href="https://github.com/ashtewari/khelcoin" target="_blank" rel="noopener"&#10; &gt;https://github.com/ashtewari/khelcoin&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Puzzle Icon Credit: &lt;a class="link" href="https://www.flaticon.com/free-icons/puzzle" target="_blank" rel="noopener"&#10; &gt;https://www.flaticon.com/free-icons/puzzle&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Universal Windows Platform Application on Raspberry Pi</title><link>https://www.tewari.info/2020/05/22/universal-windows-platform-application-on-raspberry-pi/</link><pubDate>Sat, 23 May 2020 02:57:40 +0000</pubDate><guid>https://www.tewari.info/2020/05/22/universal-windows-platform-application-on-raspberry-pi/</guid><description>&lt;p&gt;Universal Windows Platform (UWP) provides a common app platform on every device that runs Windows 10. The core APIs in UWP are the same on all Windows devices – including Desktop PC, Mobile Phone, XBox, Hololens, IOT devices and &lt;a class="link" href="https://docs.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide" target="_blank" rel="noopener"&#10; &gt;others&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;You can target specific device capabilities of a device family with &lt;a class="link" href="https://docs.microsoft.com/en-us/uwp/extension-sdks/device-families-overview#extension-sdks" target="_blank" rel="noopener"&#10; &gt;extension SDKs&lt;/a&gt;, but you don&amp;rsquo;t have to do that if you are only using core APIs. Those core APIs include a very &lt;a class="link" href="https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/" target="_blank" rel="noopener"&#10; &gt;impressive&lt;/a&gt; set of UI capabilities. What that means is that you can create a UWP application using C# and XAML which will run on an ARM based processor on Raspberry Pi – because &lt;a class="link" href="https://docs.microsoft.com/en-us/windows/iot-core/develop-your-app/buildingappsforiotcore" target="_blank" rel="noopener"&#10; &gt;Windows 10 IOT Core&lt;/a&gt; runs on that.&lt;/p&gt;&#10;&lt;p&gt;Universal Windows Platform (&lt;a class="link" href="https://docs.microsoft.com/en-us/windows/apps/desktop/choose-your-platform" target="_blank" rel="noopener"&#10; &gt;UWP&lt;/a&gt;) has come &lt;a class="link" href="https://www.tewari.info/2014/11/12/universal-apps/" &gt;a long way&lt;/a&gt;. I decided to dust off my good old Raspberry Pi 2B and take it for a spin last weekend.&lt;/p&gt;&#10;&lt;p&gt;The goal was simply to create a Hello World UWP application, deploy it on Windows 10 IOT running on Raspberry Pi (RPi), without using any device specific extension SDKs. Here is the stack -&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide" target="_blank" rel="noopener"&#10; &gt;Universal Windows Platform App&lt;/a&gt; (C#/XAML)&lt;/li&gt;&#10;&lt;li&gt;Raspberry Pi &lt;a class="link" href="https://www.raspberrypi.org/products/raspberry-pi-2-model-b/" target="_blank" rel="noopener"&#10; &gt;Model 2B&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/windows/iot-core/windows-iot-core" target="_blank" rel="noopener"&#10; &gt;Windows 10 IOT Core&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.syncfusion.com/uwp-ui-controls" target="_blank" rel="noopener"&#10; &gt;Syncfusion Essential Studio&lt;/a&gt; &lt;a class="link" href="https://help.syncfusion.com/common/essential-studio/release-notes/v18.1.0.42" target="_blank" rel="noopener"&#10; &gt;18.1.0.42&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://visualstudio.microsoft.com/" target="_blank" rel="noopener"&#10; &gt;Visual Studio&lt;/a&gt; 2019&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/windows/uwp/get-started/create-a-hello-world-app-xaml-universal" target="_blank" rel="noopener"&#10; &gt;Create&lt;/a&gt; a simple UWP application in Visual Studio 2019.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;a class="link" href="https://docs.microsoft.com/en-us/windows/iot-core/tutorials/rpi" target="_blank" rel="noopener"&#10; &gt;Install&lt;/a&gt; Windows 10 IOT Core on Raspberry Pi 2B and connect the device to a 50&amp;quot; screen TV.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-5.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/Rpi2BOnTV.jpg"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Select ARM in the target platform drop down and Click on Remote Machine to Run/Debug Application on RPi device.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-1.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Select RPi device for deployment.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-2.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Deployment of UWP on RPi is complete.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-3.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;UWP application running on RPi.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image.jpeg"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I updated the UWP application to include &lt;a class="link" href="https://help.syncfusion.com/uwp/charts/getting-started?cs-save-lang=1&amp;amp;cs-lang=csharp" target="_blank" rel="noopener"&#10; &gt;Syncfusion Chart&lt;/a&gt; control. Did you know that you can get a &lt;a class="link" href="https://www.syncfusion.com/products/communitylicense" target="_blank" rel="noopener"&#10; &gt;Community License&lt;/a&gt; for the entire product line (including Blazor, Xamarin and UWP controls) from Syncfusion?&lt;/p&gt;&#10;&lt;p&gt;UWP application is now running on RPi, attached to the Visual Studio debugger running on the development machine (Windows Surface).&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-1.jpeg"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;No device specific extension SDKs were referenced, just the core UWP APIs were used.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-6.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Code is on &lt;a class="link" href="https://github.com/ashtewari/CoronaViewer/blob/master/CoronaViewer/MainPage.xaml.cs" target="_blank" rel="noopener"&#10; &gt;github&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>PackRaft with Xamarin and Microsoft Azure</title><link>https://www.tewari.info/2015/12/19/packraft-with-xamarin-and-microsoft-azure/</link><pubDate>Sun, 20 Dec 2015 03:22:26 +0000</pubDate><guid>https://www.tewari.info/2015/12/19/packraft-with-xamarin-and-microsoft-azure/</guid><description>&lt;p&gt;PackRaft is a cross-platform mobile application built with &lt;a class="link" href="http://developer.xamarin.com/" target="_blank" rel="noopener"&#10; &gt;Xamarin&lt;/a&gt;. It integrates with Microsoft Azure services, such as &lt;a class="link" href="https://azure.microsoft.com/en-us/services/storage/tables/" target="_blank" rel="noopener"&#10; &gt;Azure Table Storage&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/" target="_blank" rel="noopener"&#10; &gt;Azure App Service&lt;/a&gt;. It is created with &lt;a class="link" href="http://developer.xamarin.com/guides/cross-platform/xamarin-forms/" target="_blank" rel="noopener"&#10; &gt;Xamarin.Forms&lt;/a&gt; for iOS and Android (Windows version will be coming soon).&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/packraft-android-riders.png" &gt;&lt;img alt="packraft-android-riders" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/packraft-android-riders.png"&gt;&lt;/a&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/packraft-iphone-assignments.png" &gt;&lt;img alt="packraft-iphone-assignments" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/packraft-iphone5.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;The purpose of creating this sample application was to expand expertise in cross-platform mobile development with Windows Azure integration, specifically with Xamarin.Forms and Azure App Service. With Xamarin.Forms, it is possible to achieve close to 100% code reuse across these platforms for certain type of apps. Azure App Service provide a set of rich cloud backend services for your mobile applications.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/PackRaft-Platform1.png" &gt;&lt;img alt="PackRaft-Platform" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/PackRaft-Platform1.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;PackRaft allows the user to setup a fleet of vehicles of various seating capacities. Groups of riders are assigned to these rides according to the count of riders in each group. You can specify that certain groups be kept together on a single vehicle - and the app tries to honor those requests if possible. I did this via a rudimentary implementation of a bin packing algorithm. This implementation is deployed as an &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/api/" target="_blank" rel="noopener"&#10; &gt;API App&lt;/a&gt; in Microsoft Azure.&lt;/p&gt;&#10;&lt;p&gt;PackRaft uses &lt;a class="link" href="https://github.com/Azure/azure-mobile-services" target="_blank" rel="noopener"&#10; &gt;Azure Mobile Services SDK&lt;/a&gt; to persist Fleet data to Azure Table Storage and to authenticate users with Facebook and Microsoft accounts. Twitter and Google account authentication is also supported by Azure Mobile Services SDK out of the box and those will also be enabled in PackRaft soon.&lt;/p&gt;&#10;&lt;p&gt;PackRaft is available on Android, iPhone and iPad.&#10;&lt;a class="link" href="https://play.google.com/store/apps/details?id=com.ashtewari.packmyride&amp;amp;utm_source=global_co&amp;amp;utm_medium=prtnr&amp;amp;utm_content=Mar2515&amp;amp;utm_campaign=PartBadge&amp;amp;pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="Get it on Google Play" class="gallery-image" data-flex-basis="810px" data-flex-grow="337" height="242" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png" srcset="https://www.tewari.info/en-play-badge_8894513479543131546_hu_5bf219cf694dcfc4.png 800w, https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png 817w" width="817"&gt;&lt;/a&gt;&lt;a class="link" href="https://itunes.apple.com/us/app/packraft/id1059614859" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="Download iOS app on App Store" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/images/Download_on_the_App_Store_Badge_US-UK_135x40.svg"&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Creating Android Apps using C# and Xamarin</title><link>https://www.tewari.info/2015/02/21/creating-android-apps-using-c-and-xamarin/</link><pubDate>Sat, 21 Feb 2015 17:30:25 +0000</pubDate><guid>https://www.tewari.info/2015/02/21/creating-android-apps-using-c-and-xamarin/</guid><description>&lt;p&gt;You can create native Android and iOS applications using C#, with Xamarin. I explored the capabilities of Xamarin tools and the development experience with Xamarin to create an Android version of CensusMapper. CensusMapper displays U.S. Census data on a Map. I created the original application for Windows 8 (WinRT) using C#. I have since converted it to an MVVM (C#) application, HTML/Javascript (WinJS) version and more recently to a &lt;a class="link" href="https://www.tewari.info/2014/11/12/universal-apps/" &gt;Universal Windows App&lt;/a&gt; running on Windows Phone 8.1 and Windows 8.1. In that tradition, now it is faithfully ported to run on Android devices, using C# and Xamarin. CensusMapper is available on &lt;a class="link" href="https://play.google.com/store/search?q=CensusMapper" target="_blank" rel="noopener"&#10; &gt;Google Play Store&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://play.google.com/store/apps/details?id=info.tewari.censusmapper" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="censusmapper-android-on-nexus5" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/censusmapper-android-on-nexus5.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;CensusMapper.Android shows population of each U.S. State. User can tap on a location on the map to show the population (as per 2010 census) of the zip code containing the selected location. User can select a location to view the population compared to the containing geography, as a pie-chart.&lt;/p&gt;&#10;&lt;p&gt;This experimental project helped me expand my cross-platform mobile application development skillset in the following areas:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Using Xamarin/C# to create an Android app using Xamarin Studio on a PC and a Mac.&lt;/li&gt;&#10;&lt;li&gt;Structuring C#/.NET source code in a Portable Class Library to allow reuse on Windows, Windows Phone, Android and iOS platforms.&lt;/li&gt;&#10;&lt;li&gt;Converting the functionality of an existing Windows Universal App (Windows Phone/ WinRT) to an Android app.&lt;/li&gt;&#10;&lt;li&gt;Using GoogleMaps API (via Google Play Services) to display data retrieved from U.S. Census API.&lt;/li&gt;&#10;&lt;li&gt;Using Bing Maps API for reverse geocoding in an Android app.&lt;/li&gt;&#10;&lt;li&gt;Creating custom markers using low-level API in Android.Graphics namespace to display information on the map.&lt;/li&gt;&#10;&lt;li&gt;Charting with &lt;a class="link" href="http://oxyplot.org/" target="_blank" rel="noopener"&#10; &gt;oxyplot&lt;/a&gt; cross-platform charting library.&lt;/li&gt;&#10;&lt;li&gt;Working with Android SDK, AVD Manager as well as developer documentation and resources on Android and Xamarin websites in the development lifecycle of the app.&lt;/li&gt;&#10;&lt;li&gt;Publishing an app on Google Play Store.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I started with Xamarin.Forms.Maps. It was quick and straight forward to create a Xamarin.Forms app. The tricky part was creating the custom markers (the shape of speech bubble) on the map, instead of using the default push-pin markers . I tried creating a CustomRender to create custom markers and built interactivity with the map elements, but it turned out to be too much hassle. All of that customization would be specific to Android platform only and not reusable on the iOS anyway. So, I decided against using Xamarin.Forms and work directly with Google Maps library. Xamarin.Forms also limits me to Windows Phone 8 compatibility in my PCL. I can&amp;rsquo;t use Windows Phone 8.1 in any PCL profile compatible with Xamarin.Forms. It also requires a minimum of Android 4.0. The PCL profile # 111 for sharing code between Windows 8, Windows Phone 8.1 as well as Android and iOS, is not compatible with Xamarin.Forms at the time of writing this.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/pcl-profile-111.png" &gt;&lt;img alt="pcl-profile-111" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/pcl-profile-111.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Don&amp;rsquo;t use the Android emulator for testing. Get a real device. I stopped using the emulator while working on CensusMapper because GooglePlayServices (GPS) dependency was making me jump through way too many hoops. Debugging map functionality on the real device was a much more productive experience. Then I discovered &lt;a class="link" href="https://xamarin.com/android-player" target="_blank" rel="noopener"&#10; &gt;Xamarin&amp;rsquo;s Android Player&lt;/a&gt;. It is awesome. By all means, use it! It is a pleasure to work with, especially on a Mac. Don&amp;rsquo;t be discouraged by the preview/beta status. It blows the Android SDK virtual devices out of the water, in terms of performance and usability.&lt;/p&gt;&#10;&lt;p&gt;I tested on &lt;a class="link" href="https://play.google.com/store/devices/details/Nexus_4_8GB" target="_blank" rel="noopener"&#10; &gt;Google Nexus 4&lt;/a&gt; (Android 4.2), &lt;a class="link" href="http://www.amazon.com/Samsung-SCH-i535-Verizon-Unlocked-Smartphone/dp/B00O4CZ15O" target="_blank" rel="noopener"&#10; &gt;Samsung Galaxy SIII&lt;/a&gt; (Android 4.3) , &lt;a class="link" href="http://www.amazon.com/gp/product/B00LUZ8WJA" target="_blank" rel="noopener"&#10; &gt;BLU Dash JR&lt;/a&gt; (Android 4.4) and an older &lt;a class="link" href="http://www.amazon.com/Coby-7-Inch-Android-Internet-Touchscreen-Tablet/dp/B005HUH88K" target="_blank" rel="noopener"&#10; &gt;Coby Kyros&lt;/a&gt; tablet (Android 2.2). Google Play Services update, required for the app to work, could not be installed on BLU device because of the limited storage space. You must root the device in order to install the GPS updates on an SD card. It is advisable to have a &lt;a class="link" href="http://unrevoked.com/rootwiki/doku.php/public/root_friendly" target="_blank" rel="noopener"&#10; &gt;root-friendly device&lt;/a&gt;. Google Nexus devices are excellent development devices, allowing you root access and the ability to load custom ROMs. However, it is very important for you to get a few different kinds of devices of various form factors and Android versions/API levels for testing your app&amp;rsquo;s functional and non-functional aspects.&lt;/p&gt;&#10;&lt;p&gt;Android device logs and the IDE build logs are the most important tools you have to resolve problems encountered in the Android app development with Xamarin. These should be the first places to look when troubleshooting issue.&lt;/p&gt;&#10;&lt;p&gt;Versions used in this project:&#10;Xamarin 3.8.&#10;Xamarin Studio 5.7&#10;Xamarin Android 4.20.0&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://play.google.com/store/apps/details?id=info.tewari.censusmapper&amp;amp;utm_source=global_co&amp;amp;utm_medium=prtnr&amp;amp;utm_content=Mar2515&amp;amp;utm_campaign=PartBadge&amp;amp;pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="Get it on Google Play" class="gallery-image" data-flex-basis="810px" data-flex-grow="337" height="242" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png" srcset="https://www.tewari.info/en-play-badge_8894513479543131546_hu_5bf219cf694dcfc4.png 800w, https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png 817w" width="817"&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Universal Apps</title><link>https://www.tewari.info/2014/11/12/universal-apps/</link><pubDate>Thu, 13 Nov 2014 02:50:36 +0000</pubDate><guid>https://www.tewari.info/2014/11/12/universal-apps/</guid><description>&lt;p&gt;The story of Windows Universal Apps is the result of advancements in three areas:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The platform convergence between Windows 8.1 and Windows Phone 8.1&lt;/li&gt;&#10;&lt;li&gt;The (ongoing) unification of Windows and Windows Phone stores.&lt;/li&gt;&#10;&lt;li&gt;Shared Projects and other enhancements in Visual Studio 2013 Update 2 to support cross-platform development.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/universal-apps-titile.png" &gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/universal-apps-titile.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;I have written about my experience in converting an existing Windows 8 app, &lt;a class="link" href="http://blog.appliedis.com/2012/10/22/censusmapper-app-mashup-census-data-with-bing-maps/" target="_blank" rel="noopener"&#10; &gt;CensusMapper&lt;/a&gt;, to a Universal App on the &lt;a class="link" href="http://blog.appliedis.com/author/ash-tewari/" target="_blank" rel="noopener"&#10; &gt;AIS Blog&lt;/a&gt;. &lt;a class="link" href="http://blog.appliedis.com/2014/10/05/censusmapper-is-a-universal-app/" target="_blank" rel="noopener"&#10; &gt;Read the article here&lt;/a&gt;. I have also created another Universal App, LRMS, using Windows &lt;a class="link" href="http://appstudio.windows.com/" target="_blank" rel="noopener"&#10; &gt;App Studio&lt;/a&gt;. This app is available in both &lt;a class="link" href="http://apps.microsoft.com/windows/en-us/app/454df9ae-7a9d-40f2-8caf-5266649939d9" target="_blank" rel="noopener"&#10; &gt;Windows&lt;/a&gt; and &lt;a class="link" href="http://www.windowsphone.com/en-us/store/app/lrms/497cb5bb-1d37-4c66-a200-912b39872f6c" target="_blank" rel="noopener"&#10; &gt;Windows Phone&lt;/a&gt; Stores.&lt;/p&gt;&#10;&lt;p&gt;I gave a presentation at the &lt;a class="link" href="http://www.codecamp.org/Raleigh/" target="_blank" rel="noopener"&#10; &gt;Raleigh Code Camp&lt;/a&gt; this past weekend : Creating Universal Apps for Windows Phone 8.1 and Windows 8.1. Thanks to everyone who attended the presentation. The session slides for this presentation are available here : &lt;a class="link" href="http://1drv.ms/1GQSJI6" target="_blank" rel="noopener"&#10; &gt;Download Slides&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>Announcing CodeDemo</title><link>https://www.tewari.info/2014/03/09/announcing-codedemo/</link><pubDate>Mon, 10 Mar 2014 01:58:22 +0000</pubDate><guid>https://www.tewari.info/2014/03/09/announcing-codedemo/</guid><description>&lt;p&gt;It is a challenge to use code snippets in presentations, demos and live coding sessions. I have struggled with this and seen many speakers jumping through hoops to assimilate code snippets in their demos. I had to create something to ease this pain.&lt;/p&gt;&#10;&lt;p&gt;Today, I am announcing the general availability of &lt;a class="link" href="http://codedemo.net/" title="CodeDemo website"&#10; target="_blank" rel="noopener"&#10; &gt;CodeDemo&lt;/a&gt;. A tool for instructors, presenters and speakers who create instructional demos, record screen-casts or do live presentations. CodeDemo pastes code snippets into your favorite IDE without getting in your way.&lt;/p&gt;&#10;&lt;p&gt;I decided not to open-source CodeDemo to challenge myself to an exercise in commercialization. I decided to learn about entrepreneurship, marketing, sales, licensing and other aspects of software commercialization. It has been an MBA&amp;rsquo;s worth of education so far. This journey is not over yet. I am taking the &lt;a class="link" href="http://theleanstartup.com/principles" title="The Lean Startup"&#10; target="_blank" rel="noopener"&#10; &gt;Lean&lt;/a&gt; approach of creating a &lt;a class="link" href="http://en.wikipedia.org/wiki/Minimum_viable_product" title="minimum viable product"&#10; target="_blank" rel="noopener"&#10; &gt;Minimum Viable Product&lt;/a&gt;. The future of CodeDemo, its feature set and the price, will be shaped by the traction it gets in the market, the feedback from the customers as well as patterns of use reported by the users.&lt;/p&gt;&#10;&lt;p&gt;Head over to &lt;a class="link" href="http://codedemo.net" target="_blank" rel="noopener"&#10; &gt;http://codedemo.net&lt;/a&gt; to grab a copy for yourself. Let me know if find it useful - I will be very excited to hear from you.&lt;/p&gt;&#10;&lt;p&gt;Here is a quick 2 minute introduction to CodeDemo :&lt;/p&gt;&#10;&lt;p&gt;[youtube]aFn3boBAWjw[/youtube]&lt;/p&gt;&#10;</description></item><item><title>Windigo 1.1 Released</title><link>https://www.tewari.info/2011/12/05/windigo-1-1-released/</link><pubDate>Mon, 05 Dec 2011 13:59:47 +0000</pubDate><guid>https://www.tewari.info/2011/12/05/windigo-1-1-released/</guid><description>&lt;p&gt;&lt;a class="link" href="http://www.windowsphone.com/en-US/apps/fa40440c-a974-4329-b650-cf46d7c6eae4" target="_blank" rel="noopener"&#10; &gt;&lt;img alt="windigo-samsung" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/windigo_10-3-2011_0.52.40.563.png"&gt;&lt;/a&gt;&lt;a class="link" href="http://www.windowsphone.com/en-US/apps/fa40440c-a974-4329-b650-cf46d7c6eae4" title="windigo-marketplace"&#10; target="_blank" rel="noopener"&#10; &gt;Windigo&lt;/a&gt; is a windows phone app to access your &lt;a class="link" href="http://www.diigo.com" title="diigo-website-url"&#10; target="_blank" rel="noopener"&#10; &gt;diigo&lt;/a&gt; account on windows phone 7.&lt;/p&gt;&#10;&lt;p&gt;Create, edit and search diigo bookmarks. Slick, to-the-point interface. Browse and search public bookmarks of other diigo users. Tap on the bookmark for details, including tags. Tap on any tag to narrow your search. Trial version is available.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="http://www.windowsphone.com/en-US/apps/fa40440c-a974-4329-b650-cf46d7c6eae4" title="windigo-download-link"&#10; target="_blank" rel="noopener"&#10; &gt;Windigo 1.1&lt;/a&gt; is out on the windows phone marketplace now.  Highlights of this update :&lt;/p&gt;&#10;&lt;p&gt;- Last SearchScope used is now persistent&lt;/p&gt;&#10;&lt;p&gt;- Fixed search when a search term is picked from suggested words&lt;/p&gt;&#10;&lt;p&gt;- Problem report now includes system info&lt;/p&gt;&#10;&lt;p&gt;- Handle crash on navigating from main page&lt;/p&gt;&#10;&lt;p&gt;- Fixed feature tracking for analytics&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Last SearchScope used is now persistent&lt;/li&gt;&#10;&lt;li&gt;Fixed search when a search term is picked from suggested words&lt;/li&gt;&#10;&lt;li&gt;Problem report now includes system info&lt;/li&gt;&#10;&lt;li&gt;Handle crash on navigating from main page&lt;/li&gt;&#10;&lt;li&gt;Fixed feature tracking for analytics&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>