44 < meta charset ="utf-8 " />
55 < meta name ="viewport " content ="width=device-width,initial-scale=1,viewport-fit=cover " />
66 < title > Python Tutor — Private Python practice with a local AI tutor</ title >
7- < meta name ="description " content ="A private, offline Python tutor that runs entirely on your own machine. Lessons, an interactive code lab, and a local AI mentor — no accounts, no cloud, no telemetry. " />
7+ < meta name ="description " content ="A private, offline Python tutor that runs entirely on your own machine. Lessons, an interactive code lab, and a local AI mentor — no accounts, no cloud, no telemetry. Clone the repo and run two commands to start. " />
88 < meta name ="theme-color " content ="#0c0c0d " />
99 < link rel ="icon " type ="image/svg+xml " href ="./assets/favicon.svg " />
10- < link rel ="canonical " href ="https://github.com/StewAlexander-com/ python-tutor " />
10+ < link rel ="canonical " href ="https://stewalexander-com. github.io/ python-tutor/ " />
1111
1212 <!-- Open Graph -->
1313 < meta property ="og:type " content ="website " />
1616 < meta property ="og:image " content ="./assets/og-image.png " />
1717 < meta property ="og:image:width " content ="1200 " />
1818 < meta property ="og:image:height " content ="630 " />
19- < meta property ="og:url " content ="https://github.com/StewAlexander-com/ python-tutor " />
19+ < meta property ="og:url " content ="https://stewalexander-com. github.io/ python-tutor/ " />
2020
2121 <!-- Twitter / X -->
2222 < meta name ="twitter:card " content ="summary_large_image " />
4343 < a class ="topbar__link " href ="#why "> Why</ a >
4444 < a class ="topbar__link " href ="#loop "> How it works</ a >
4545 < a class ="topbar__link " href ="#screens "> See it</ a >
46- < a class ="topbar__link " href ="#start "> Start </ a >
46+ < a class ="topbar__link " href ="#start "> Install </ a >
4747 </ nav >
4848 < a class ="btn btn--ghost topbar__cta " href ="https://github.com/StewAlexander-com/python-tutor " rel ="noopener ">
4949 < svg width ="16 " height ="16 " viewBox ="0 0 24 24 " aria-hidden ="true " fill ="currentColor "> < path d ="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.25 3.34.96.1-.74.4-1.25.72-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.45.11-3.03 0 0 .97-.31 3.18 1.18a11.04 11.04 0 0 1 5.8 0c2.21-1.49 3.18-1.18 3.18-1.18.62 1.58.23 2.74.11 3.03.73.81 1.18 1.84 1.18 3.1 0 4.42-2.7 5.39-5.27 5.68.41.36.78 1.05.78 2.12v3.14c0 .31.21.68.8.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z "/> </ svg >
@@ -71,8 +71,8 @@ <h1 class="hero__title">
7171 < strong > never leave your laptop</ strong > .
7272 </ p >
7373 < div class ="hero__cta ">
74- < a class ="btn btn--primary " href ="https://github.com/StewAlexander-com/python-tutor#quick- start" rel =" noopener ">
75- < span > Get started </ span >
74+ < a class ="btn btn--primary " href ="# start ">
75+ < span > Install in 3 commands </ span >
7676 < svg width ="14 " height ="14 " viewBox ="0 0 24 24 " aria-hidden ="true " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < path d ="M5 12h14M13 5l7 7-7 7 "/> </ svg >
7777 </ a >
7878 < a class ="btn btn--ghost " href ="https://github.com/StewAlexander-com/python-tutor " rel ="noopener "> View on GitHub</ a >
@@ -243,29 +243,86 @@ <h2 class="section__title">See it.</h2>
243243 </ div >
244244 </ section >
245245
246- <!-- ============ TWO-COMMAND START ============ -->
246+ <!-- ============ START (clone + install + run) ============ -->
247247 < section id ="start " class ="start ">
248248 < div class ="start__inner ">
249- < h2 class ="section__title "> Two commands.</ h2 >
250- < p class ="section__lede "> macOS or Linux. Python 3.10+.</ p >
251- < div class ="start__code ">
252- < pre > < code > < span class ="t-com "> # 1 — clone</ span >
253- gh repo clone StewAlexander-com/python-tutor
254- < span class ="t-kw "> cd</ span > python-tutor
255-
256- < span class ="t-com "> # 2 — set up & serve (any host step is opt-in y/N)</ span >
257- ./install.sh
258- ./run.sh < span class ="t-com "> # → http://localhost:8001/</ span > </ code > </ pre >
259- </ div >
260- < p class ="start__note ">
261- < code > install.sh</ code > only touches the repo on its own. Installing
262- Ollama, starting the daemon, pulling the model, or launching the app
263- are < strong > opt-in y/N prompts</ strong > — press Enter and nothing
264- changes on your host.
249+ < h2 class ="section__title "> Clone, install, run.</ h2 >
250+ < p class ="section__lede ">
251+ This page is the start page for the repo & software. Three short
252+ commands and you're at < code > http://localhost:8001/</ code > .
253+ macOS or Linux. Python 3.10+.
265254 </ p >
266- < div class ="start__cta ">
255+
256+ < ol class ="start__steps ">
257+ < li class ="start__step ">
258+ < div class ="start__step-head ">
259+ < span class ="start__step-num "> 1</ span >
260+ < h3 class ="start__step-title "> Clone the repo</ h3 >
261+ </ div >
262+ < p class ="start__step-sub "> HTTPS works without a GitHub login. < code > gh repo clone</ code > works too if you use the GitHub CLI.</ p >
263+ < div class ="start__code start__code--with-copy ">
264+ < pre > < code id ="cmd-clone "> git clone https://github.com/StewAlexander-com/python-tutor.git
265+ cd python-tutor</ code > </ pre >
266+ < button class ="copy-btn " type ="button " data-copy-target ="cmd-clone " aria-label ="Copy clone commands "> Copy</ button >
267+ </ div >
268+ </ li >
269+
270+ < li class ="start__step ">
271+ < div class ="start__step-head ">
272+ < span class ="start__step-num "> 2</ span >
273+ < h3 class ="start__step-title "> Install</ h3 >
274+ </ div >
275+ < p class ="start__step-sub ">
276+ Sets up a Python venv and dependencies. Any host-level step
277+ (Ollama install, daemon start, model pull, app launch) is an
278+ < strong > opt-in y/N prompt</ strong > — press Enter and nothing
279+ changes on your host.
280+ </ p >
281+ < div class ="start__code start__code--with-copy ">
282+ < pre > < code id ="cmd-install "> ./install.sh</ code > </ pre >
283+ < button class ="copy-btn " type ="button " data-copy-target ="cmd-install " aria-label ="Copy install command "> Copy</ button >
284+ </ div >
285+ </ li >
286+
287+ < li class ="start__step ">
288+ < div class ="start__step-head ">
289+ < span class ="start__step-num "> 3</ span >
290+ < h3 class ="start__step-title "> Run & open in your browser</ h3 >
291+ </ div >
292+ < p class ="start__step-sub "> < code > --open-browser</ code > pops the tab once < code > /api/health</ code > is green.</ p >
293+ < div class ="start__code start__code--with-copy ">
294+ < pre > < code id ="cmd-run "> ./run.sh --open-browser</ code > </ pre >
295+ < button class ="copy-btn " type ="button " data-copy-target ="cmd-run " aria-label ="Copy run command "> Copy</ button >
296+ </ div >
297+ < p class ="start__step-sub start__step-sub--muted ">
298+ Or just < code > ./run.sh</ code > and open < code > http://localhost:8001/</ code > yourself.
299+ </ p >
300+ </ li >
301+ </ ol >
302+
303+ < details class ="start__more ">
304+ < summary > Common variations</ summary >
305+ < div class ="start__code start__code--with-copy ">
306+ < pre > < code id ="cmd-more "> < span class ="t-com "> # trusted host: install Ollama, pull model, launch — no prompts</ span >
307+ ./install.sh --yes
308+
309+ < span class ="t-com "> # CI / air-gapped: never prompt, default everything to "no"</ span >
310+ ./install.sh --noninteractive
311+
312+ < span class ="t-com "> # Python-only setup (skip every Ollama probe)</ span >
313+ ./install.sh --skip-ollama
314+
315+ < span class ="t-com "> # pick a different model or port</ span >
316+ ./install.sh --model llama3.1:8b
317+ ./run.sh --port 8042</ code > </ pre >
318+ < button class ="copy-btn " type ="button " data-copy-target ="cmd-more " aria-label ="Copy variation commands "> Copy</ button >
319+ </ div >
320+ </ details >
321+
322+ < div class ="start__links ">
267323 < a class ="btn btn--primary " href ="https://github.com/StewAlexander-com/python-tutor " rel ="noopener "> Open the repo</ a >
268- < a class ="btn btn--ghost " href ="https://github.com/StewAlexander-com/python-tutor#quick-start " rel ="noopener "> Full quick start →</ a >
324+ < a class ="btn btn--ghost " href ="https://github.com/StewAlexander-com/python-tutor#readme " rel ="noopener "> Read the README</ a >
325+ < a class ="btn btn--ghost " href ="https://github.com/StewAlexander-com/python-tutor/issues " rel ="noopener "> File an issue</ a >
269326 </ div >
270327 </ div >
271328 </ section >
@@ -290,5 +347,46 @@ <h2 class="section__title">Two commands.</h2>
290347 < p class ="foot__note "> MIT-licensed. Frontend adapted from < a href ="https://github.com/StewAlexander-com/Python-Power-User " rel ="noopener "> Python Power User</ a > .</ p >
291348 </ div >
292349 </ footer >
350+
351+ < script >
352+ ( function ( ) {
353+ var buttons = document . querySelectorAll ( '.copy-btn[data-copy-target]' ) ;
354+ buttons . forEach ( function ( btn ) {
355+ btn . addEventListener ( 'click' , function ( ) {
356+ var id = btn . getAttribute ( 'data-copy-target' ) ;
357+ var node = document . getElementById ( id ) ;
358+ if ( ! node ) return ;
359+ var text = node . innerText . replace ( / / g, ' ' ) ;
360+ var done = function ( ) {
361+ var original = btn . textContent ;
362+ btn . textContent = 'Copied' ;
363+ btn . classList . add ( 'is-copied' ) ;
364+ setTimeout ( function ( ) {
365+ btn . textContent = original ;
366+ btn . classList . remove ( 'is-copied' ) ;
367+ } , 1400 ) ;
368+ } ;
369+ if ( navigator . clipboard && navigator . clipboard . writeText ) {
370+ navigator . clipboard . writeText ( text ) . then ( done , function ( ) {
371+ fallbackCopy ( text ) ; done ( ) ;
372+ } ) ;
373+ } else {
374+ fallbackCopy ( text ) ; done ( ) ;
375+ }
376+ } ) ;
377+ } ) ;
378+ function fallbackCopy ( text ) {
379+ var ta = document . createElement ( 'textarea' ) ;
380+ ta . value = text ;
381+ ta . setAttribute ( 'readonly' , '' ) ;
382+ ta . style . position = 'absolute' ;
383+ ta . style . left = '-9999px' ;
384+ document . body . appendChild ( ta ) ;
385+ ta . select ( ) ;
386+ try { document . execCommand ( 'copy' ) ; } catch ( e ) { }
387+ document . body . removeChild ( ta ) ;
388+ }
389+ } ) ( ) ;
390+ </ script >
293391</ body >
294392</ html >
0 commit comments