progy 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,28 @@
1
1
  // @bun
2
- var ce=Object.create;var{getPrototypeOf:ue,defineProperty:W,getOwnPropertyNames:pe}=Object;var le=Object.prototype.hasOwnProperty;var Le=(e,s,t)=>{t=e!=null?ce(ue(e)):{};let n=s||!e||!e.__esModule?W(t,"default",{value:e,enumerable:!0}):t;for(let o of pe(e))if(!le.call(n,o))W(n,o,{get:()=>e[o],enumerable:!0});return n};var be=(e,s)=>()=>(s||e((s={exports:{}}).exports,s),s.exports);var ze=import.meta.require;var{serve:Fe}=globalThis.Bun;import{join as G}from"path";var J={"/api/health":Response.json({status:"ok"})};import{readdir as L,readFile as v,writeFile as Y,mkdir as Q,exists as P}from"fs/promises";import{join as d}from"path";import{homedir as fe}from"os";import{spawn as de}from"child_process";var j=process.env.PROG_CWD||process.cwd(),me=d(fe(),".progy"),B=d(me,"config.json"),q=d(j,"course.json"),_=d(j,".progy"),ge=d(_,"exercises.json"),A=d(_,"progress.json"),V=process.env.PROGY_OFFLINE==="true",I={stats:{totalXp:0,currentStreak:0,longestStreak:0,lastActiveDate:null},exercises:{},quizzes:{},achievements:[]},u=null,b=null,U=0;async function ye(){try{if(await P(q)){let e=await v(q,"utf-8");return JSON.parse(e)}return null}catch(e){return console.warn(`[WARN] Failed to read course.json: ${e}`),null}}async function R(){if(!u)u=await ye();return u}async function Z(){if(await P(B))return JSON.parse(await v(B,"utf-8"));return null}async function k(){if(V){try{if(await P(A)){let s=await v(A,"utf-8"),t=JSON.parse(s);return console.log(`[OFFLINE] Loaded local progress. XP: ${t?.stats.totalXp}`),t}}catch(s){console.warn(`[WARN] Failed to read ${A}: ${s}`)}return JSON.parse(JSON.stringify(I))}await R();let e=await Z();if(e?.token&&u?.id){console.log(`[ONLINE] Fetching progress for ${u.id}...`);try{let s=await he(u.id,e.token);if(s)return console.log(`[ONLINE] Loaded cloud progress. XP: ${s.stats.totalXp}`),s;else return console.log("[ONLINE] No existing cloud progress found. Returning default."),JSON.parse(JSON.stringify(I))}catch(s){return console.error(`[CRITICAL] Failed to fetch cloud progress: ${s}`),JSON.parse(JSON.stringify(I))}}return JSON.parse(JSON.stringify(I))}async function $(e){if(V){try{await Q(_,{recursive:!0}),await Y(A,JSON.stringify(e,null,2))}catch(t){console.error(`[ERROR] Failed to save local progress: ${t}`)}return}await R();let s=await Z();if(s?.token&&u?.id)await we(u.id,e,s.token)}async function we(e,s,t){let n=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let o=await fetch(`${n}/api/progress/sync`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify({courseId:e,data:s})});if(o.ok)console.log("[ONLINE] Successfully saved to cloud.");else console.warn(`[ONLINE] Cloud save failed: ${o.status}`)}catch(o){console.error(`[ONLINE] Connection error during save: ${o}`)}}async function he(e,s){let t=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let n=await fetch(`${t}/api/progress/get?courseId=${e}`,{headers:{Authorization:`Bearer ${s}`}});if(n.ok)return await n.json();if(n.status===404)return null;throw Error(`Cloud fetch failed with ${n.status}`)}catch(n){throw n}}function F(e){let s=new Date().toISOString().split("T")[0];if(e.lastActiveDate===s)return e;let t=new Date;t.setDate(t.getDate()-1);let n=t.toISOString().split("T")[0];if(e.lastActiveDate===n)e.currentStreak+=1;else e.currentStreak=1;if(e.currentStreak>e.longestStreak)e.longestStreak=e.currentStreak;return e.lastActiveDate=s,e}var X=(e)=>e.replace(/_/g," ").replace(/([a-zA-Z])(\d+)/g,"$1 $2").replace(/\b\w/g,(s)=>s.toUpperCase());async function K(e){if(b&&Date.now()-U<5000)return b;let s=e.content.exercises,t=d(j,s);if(!await P(t))return{};let o=(await L(t)).filter((i)=>!i.startsWith(".")&&i!=="README.md"&&i!=="mod.rs"&&i!=="practice");o.sort((i,r)=>{let w=parseInt(i.split("_")[0]||"999"),g=parseInt(r.split("_")[0]||"999");return w-g});let c={};async function m(i,r,w,g,h,l,S){let y=X(w);if(h[w]?.title)y=h[w].title;let x=d(l,r.name);if(r.isDirectory()){let C=["exercise.rs","main.rs","index.ts","main.go","index.js"];for(let O of C){let a=d(x,O);if(await P(a)){x=a;break}}}if(await P(x)&&(await Bun.file(x).stat()).isFile())try{let O=(await v(x,"utf-8")).match(/\/\/\s*(?:Title|title):\s*(.+)/);if(O&&O[1])y=O[1].trim()}catch{}let E={id:`${i}/${r.name}`,module:i,moduleTitle:g,name:r.name,exerciseName:w,friendlyName:y,path:d(l,r.name)};if(r.isDirectory()){let C=d(l,r.name,"quiz.json");S[i].push({...E,markdownPath:d(l,r.name,"README.md"),hasQuiz:await P(C),type:"directory"})}else if(r.isFile()){if(r.name.endsWith(".test.ts")||r.name==="package.json")return;S[i].push({...E,markdownPath:null,type:"file"})}}for(let i of o){let r=d(t,i);if((await Bun.file(r).stat()).isDirectory()){c[i]=[];let g=await L(r,{withFileTypes:!0}),h=X(i),l={},S=d(r,"info.toml");if(await P(S))try{let a=await v(S,"utf-8"),f=Bun.TOML.parse(a);if(f.module?.message)h=f.module.message;if(Array.isArray(f.exercises)){for(let N of f.exercises)if(N.name)l[N.name]=N}else if(f.exercises&&typeof f.exercises==="object")for(let[N,T]of Object.entries(f.exercises))l[N]=typeof T==="string"?{title:T}:T}catch(a){console.warn(`[WARN] Failed to parse info.toml: ${a}`)}let y=(a)=>{let f=a.match(/^(\d+)_/);return f?parseInt(f[1]||"0"):9999},x=new Map;for(let a of g){if(a.name.startsWith(".")||a.name==="README.md"||a.name==="mod.rs"||a.name==="info.toml")continue;x.set(a.name.split(".")[0]||"",a)}let E=Object.keys(l),C=new Set;for(let a of E){let f=x.get(a);if(f)C.add(a),await m(i,f,a,h,l,r,c)}let O=g.filter((a)=>{let f=a.name.split(".")[0]||"";return!C.has(f)&&!a.name.startsWith(".")&&a.name!=="README.md"&&a.name!=="mod.rs"&&a.name!=="info.toml"});O.sort((a,f)=>{let N=y(a.name||""),T=y(f.name||"");return N!==T?N-T:(a.name||"").localeCompare(f.name||"")});for(let a of O)await m(i,a,a.name.split(".")[0]||"",h,l,r,c)}}let p=d(t,"practice");if(await P(p)){c.practice=[];let i=await L(p);for(let r of i)if(r.endsWith(".rs")||r.endsWith(".ts")||r.endsWith(".js")||r.endsWith(".go"))c.practice.push({id:`practice/${r}`,module:"practice",name:r,exerciseName:r.split(".")[0],path:d(p,r),markdownPath:null,type:"file"})}return await Q(_,{recursive:!0}),await Y(ge,JSON.stringify(c,null,2)),b=c,U=Date.now(),c}async function ee(e){let s=[];for(let t of e.checks)if(t.type==="command")try{let n=t.command.split(" "),o=n[0];if(!o)continue;let c=de(o,n.slice(1),{stdio:"ignore"}),m=await new Promise((p)=>{c.on("close",(i)=>p(i===0)),c.on("error",()=>p(!1))});s.push({name:t.name,status:m?"pass":"fail",message:m?"Found":"Not found"})}catch(n){s.push({name:t.name,status:"fail",message:String(n)})}return s}function se(e,s){try{let n=null,o=e.match(/__SRP_BEGIN__\s*([\s\S]*?)\s*__SRP_END__/);if(o&&o[1])n=o[1].trim();else{let c=e.match(/\{[\s\S]*\}/g);if(c&&c.length>0)n=c[c.length-1]?.trim()??null}if(n){let c=n.indexOf("{"),m=n.lastIndexOf("}");if(c!==-1&&m!==-1){let p=JSON.parse(n.substring(c,m+1)),i=`## ${p.success?"\u2705 Success":"\u274C Failed"}
2
+ var fe=Object.create;var{getPrototypeOf:de,defineProperty:X,getOwnPropertyNames:me}=Object;var ge=Object.prototype.hasOwnProperty;var Be=(e,t,s)=>{s=e!=null?fe(de(e)):{};let n=t||!e||!e.__esModule?X(s,"default",{value:e,enumerable:!0}):s;for(let o of me(e))if(!ge.call(n,o))X(n,o,{get:()=>e[o],enumerable:!0});return n};var Ue=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var qe=import.meta.require;var{serve:He}=globalThis.Bun;import{join as b}from"path";var Y={"/api/health":Response.json({status:"ok"})};import{readdir as z,readFile as k,writeFile as W,mkdir as J,exists as x}from"fs/promises";import{join as d}from"path";import{homedir as ye}from"os";import{spawn as we}from"child_process";var T=process.env.PROG_CWD||process.cwd(),K=d(ye(),".progy"),H=d(K,"config.json"),Q=d(T,"course.json"),$=d(T,".progy"),he=d($,"exercises.json"),_=d($,"progress.json"),ee=process.env.PROGY_OFFLINE==="true",A={stats:{totalXp:0,currentStreak:0,longestStreak:0,lastActiveDate:null},exercises:{},quizzes:{},achievements:[]},u=null,M=null,V=0;async function Se(){try{if(await x(Q)){let e=await k(Q,"utf-8");return JSON.parse(e)}return null}catch(e){return console.warn(`[WARN] Failed to read course.json: ${e}`),null}}async function R(){if(!u)u=await Se();return u}async function v(){if(await x(H))return JSON.parse(await k(H,"utf-8"));return{}}async function F(e){let s={...await v(),...e};return await J(K,{recursive:!0}),await W(H,JSON.stringify(s,null,2)),s}async function E(){if(ee){try{if(await x(_)){let t=await k(_,"utf-8"),s=JSON.parse(t);return console.log(`[OFFLINE] Loaded local progress. XP: ${s?.stats.totalXp}`),s}}catch(t){console.warn(`[WARN] Failed to read ${_}: ${t}`)}return JSON.parse(JSON.stringify(A))}await R();let e=await v();if(e?.token&&u?.id){console.log(`[ONLINE] Fetching progress for ${u.id}...`);try{let t=await xe(u.id,e.token);if(t)return console.log(`[ONLINE] Loaded cloud progress. XP: ${t.stats.totalXp}`),t;else return console.log("[ONLINE] No existing cloud progress found. Returning default."),JSON.parse(JSON.stringify(A))}catch(t){return console.error(`[CRITICAL] Failed to fetch cloud progress: ${t}`),JSON.parse(JSON.stringify(A))}}return JSON.parse(JSON.stringify(A))}async function D(e){if(ee){try{await J($,{recursive:!0}),await W(_,JSON.stringify(e,null,2))}catch(s){console.error(`[ERROR] Failed to save local progress: ${s}`)}return}await R();let t=await v();if(t?.token&&u?.id)await Re(u.id,e,t.token)}async function Re(e,t,s){let n=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let o=await fetch(`${n}/api/progress/sync`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({courseId:e,data:t})});if(o.ok)console.log("[ONLINE] Successfully saved to cloud.");else console.warn(`[ONLINE] Cloud save failed: ${o.status}`)}catch(o){console.error(`[ONLINE] Connection error during save: ${o}`)}}async function xe(e,t){let s=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let n=await fetch(`${s}/api/progress/get?courseId=${e}`,{headers:{Authorization:`Bearer ${t}`}});if(n.ok)return await n.json();if(n.status===404)return null;throw Error(`Cloud fetch failed with ${n.status}`)}catch(n){throw n}}function G(e){let t=new Date().toISOString().split("T")[0];if(e.lastActiveDate===t)return e;let s=new Date;s.setDate(s.getDate()-1);let n=s.toISOString().split("T")[0];if(e.lastActiveDate===n)e.currentStreak+=1;else e.currentStreak=1;if(e.currentStreak>e.longestStreak)e.longestStreak=e.currentStreak;return e.lastActiveDate=t,e}var Z=(e)=>e.replace(/_/g," ").replace(/([a-zA-Z])(\d+)/g,"$1 $2").replace(/\b\w/g,(t)=>t.toUpperCase());async function te(e){if(M&&Date.now()-V<5000)return M;let t=e.content.exercises,s=d(T,t);if(!await x(s))return{};let o=(await z(s)).filter((a)=>!a.startsWith(".")&&a!=="README.md"&&a!=="mod.rs"&&a!=="practice");o.sort((a,r)=>{let w=parseInt(a.split("_")[0]||"999"),g=parseInt(r.split("_")[0]||"999");return w-g});let c={};async function m(a,r,w,g,h,l,S){let y=Z(w);if(h[w]?.title)y=h[w].title;let P=d(l,r.name);if(r.isDirectory()){let j=["exercise.rs","main.rs","index.ts","main.go","index.js"];for(let O of j){let i=d(P,O);if(await x(i)){P=i;break}}}if(await x(P)&&(await Bun.file(P).stat()).isFile())try{let O=(await k(P,"utf-8")).match(/\/\/\s*(?:Title|title):\s*(.+)/);if(O&&O[1])y=O[1].trim()}catch{}let I={id:`${a}/${r.name}`,module:a,moduleTitle:g,name:r.name,exerciseName:w,friendlyName:y,path:d(l,r.name)};if(r.isDirectory()){let j=d(l,r.name,"quiz.json");S[a].push({...I,markdownPath:d(l,r.name,"README.md"),hasQuiz:await x(j),type:"directory"})}else if(r.isFile()){if(r.name.endsWith(".test.ts")||r.name==="package.json")return;S[a].push({...I,markdownPath:null,type:"file"})}}for(let a of o){let r=d(s,a);if((await Bun.file(r).stat()).isDirectory()){c[a]=[];let g=await z(r,{withFileTypes:!0}),h=Z(a),l={},S=d(r,"info.toml");if(await x(S))try{let i=await k(S,"utf-8"),f=Bun.TOML.parse(i);if(f.module?.message)h=f.module.message;if(Array.isArray(f.exercises)){for(let N of f.exercises)if(N.name)l[N.name]=N}else if(f.exercises&&typeof f.exercises==="object")for(let[N,C]of Object.entries(f.exercises))l[N]=typeof C==="string"?{title:C}:C}catch(i){console.warn(`[WARN] Failed to parse info.toml: ${i}`)}let y=(i)=>{let f=i.match(/^(\d+)_/);return f?parseInt(f[1]||"0"):9999},P=new Map;for(let i of g){if(i.name.startsWith(".")||i.name==="README.md"||i.name==="mod.rs"||i.name==="info.toml")continue;P.set(i.name.split(".")[0]||"",i)}let I=Object.keys(l),j=new Set;for(let i of I){let f=P.get(i);if(f)j.add(i),await m(a,f,i,h,l,r,c)}let O=g.filter((i)=>{let f=i.name.split(".")[0]||"";return!j.has(f)&&!i.name.startsWith(".")&&i.name!=="README.md"&&i.name!=="mod.rs"&&i.name!=="info.toml"});O.sort((i,f)=>{let N=y(i.name||""),C=y(f.name||"");return N!==C?N-C:(i.name||"").localeCompare(f.name||"")});for(let i of O)await m(a,i,i.name.split(".")[0]||"",h,l,r,c)}}let p=d(s,"practice");if(await x(p)){c.practice=[];let a=await z(p);for(let r of a)if(r.endsWith(".rs")||r.endsWith(".ts")||r.endsWith(".js")||r.endsWith(".go"))c.practice.push({id:`practice/${r}`,module:"practice",name:r,exerciseName:r.split(".")[0],path:d(p,r),markdownPath:null,type:"file"})}return await J($,{recursive:!0}),await W(he,JSON.stringify(c,null,2)),M=c,V=Date.now(),c}async function se(e){let t=[];for(let s of e.checks)if(s.type==="command")try{let n=s.command.split(" "),o=n[0];if(!o)continue;let c=we(o,n.slice(1),{stdio:"ignore"}),m=await new Promise((p)=>{c.on("close",(a)=>p(a===0)),c.on("error",()=>p(!1))});t.push({name:s.name,status:m?"pass":"fail",message:m?"Found":"Not found"})}catch(n){t.push({name:s.name,status:"fail",message:String(n)})}return t}function re(e,t){try{let n=null,o=e.match(/__SRP_BEGIN__\s*([\s\S]*?)\s*__SRP_END__/);if(o&&o[1])n=o[1].trim();else{let c=e.match(/\{[\s\S]*\}/g);if(c&&c.length>0)n=c[c.length-1]?.trim()??null}if(n){let c=n.indexOf("{"),m=n.lastIndexOf("}");if(c!==-1&&m!==-1){let p=JSON.parse(n.substring(c,m+1)),a=`## ${p.success?"\u2705 Success":"\u274C Failed"}
3
3
 
4
4
  > ${p.summary}
5
5
 
6
- `;if(p.diagnostics?.length){i+=`### \uD83D\uDCCD Diagnostics
6
+ `;if(p.diagnostics?.length){a+=`### \uD83D\uDCCD Diagnostics
7
7
 
8
- `;for(let r of p.diagnostics){if(i+=`#### ${r.severity==="error"?"\u274C":"\u26A0\uFE0F"} ${r.severity.toUpperCase()}
8
+ `;for(let r of p.diagnostics){if(a+=`#### ${r.severity==="error"?"\u274C":"\u26A0\uFE0F"} ${r.severity.toUpperCase()}
9
9
  **${r.message}**
10
- `,r.file)i+=`\`${r.file}:${r.line||0}\`
10
+ `,r.file)a+=`\`${r.file}:${r.line||0}\`
11
11
 
12
- `;if(r.snippet)i+=`\`\`\`rust
12
+ `;if(r.snippet)a+=`\`\`\`rust
13
13
  ${r.snippet}
14
14
  \`\`\`
15
15
 
16
- `;i+=`---
16
+ `;a+=`---
17
17
 
18
- `}}if(p.tests?.length){i+=`### \uD83E\uDDEA Tests
18
+ `}}if(p.tests?.length){a+=`### \uD83E\uDDEA Tests
19
19
 
20
- `;for(let r of p.tests)i+=`- ${r.status==="pass"?"\u2705":"\u274C"} **${r.name}**
20
+ `;for(let r of p.tests)a+=`- ${r.status==="pass"?"\u2705":"\u274C"} **${r.name}**
21
21
  ${r.message?` > ${r.message.replace(/\n/g,`
22
22
  > `)}
23
23
 
24
- `:""}`}return{success:p.success,output:p.raw.trim(),friendlyOutput:i}}}}catch{}let t=s===0&&!e.includes("\u274C");return{success:t,output:e,friendlyOutput:(t?`\u2705 Success
24
+ `:""}`}return{success:p.success,output:p.raw.trim(),friendlyOutput:a}}}}catch{}let s=t===0&&!e.includes("\u274C");return{success:s,output:e,friendlyOutput:(s?`\u2705 Success
25
25
 
26
26
  `:`\u274C Failed
27
27
 
28
- `)+e}}var Se=async()=>{return Response.json(await k())},Re=async(e)=>{try{let{type:s,id:t,success:n}=await e.json();if(!t)return Response.json({success:!1,error:"Missing ID"});let o=await k(),c=new Date().toISOString();if(s==="quiz"&&n){if(!o.quizzes[t])o.quizzes[t]={passed:!0,xpEarned:10,completedAt:c},o.stats.totalXp+=10,o.stats=F(o.stats),await $(o)}return Response.json({success:!0,progress:o})}catch(s){return Response.json({success:!1,error:String(s)})}},te={"/api/progress":{GET:Se},"/api/progress/update":{POST:Re}};var Pe=async()=>{return await R(),Response.json({...u||{},remoteApiUrl:process.env.PROGY_API_URL||"https://progy.francy.workers.dev"})},re={"/api/config":{GET:Pe}};import{readFile as D,exists as z}from"fs/promises";import{join as M}from"path";import{spawn as xe}from"child_process";var Oe=async()=>{if(await R(),!u)return Response.json({error:"No config"});let e=await K(u);return Response.json(Array.isArray(e)?{}:e)},Ne=async(e)=>{let t=new URL(e.url).searchParams.get("path");if(!t)return new Response("Missing path",{status:400});let n=M(t,"quiz.json");try{if(await z(n)){let o=await D(n,"utf-8");return Response.json(JSON.parse(o))}return Response.json({error:"Quiz not found"},{status:404})}catch(o){return Response.json({error:"Invalid quiz file"},{status:500})}},je=async(e)=>{let s=new URL(e.url),t=s.searchParams.get("path"),n=s.searchParams.get("markdownPath");if(!t)return new Response("Missing path",{status:400});try{let o="";if((await Bun.file(t).stat()).isDirectory()){let p=["exercise.rs","main.rs","index.ts","main.go","index.js"];for(let i of p){let r=M(t,i);if(await z(r)){o=await D(r,"utf-8");break}}if(!o)o="// No entry file found"}else o=await D(t,"utf-8");let m=null;if(n&&await z(n))m=await D(n,"utf-8");return Response.json({code:o,markdown:m})}catch(o){return Response.json({error:"File not found"},{status:404})}},Ce=async(e)=>{try{await R();let s=await e.json(),{exerciseName:t,id:n}=s,c=(n?.split("/")||[])[0]||"",m=u.runner.command,p=u.runner.args.map((w)=>w.replace("{{exercise}}",t).replace("{{id}}",n||"").replace("{{module}}",c)),r=(u.runner.cwd?M(j,u.runner.cwd):j).replace("{{exercise}}",t).replace("{{id}}",n||"").replace("{{module}}",c);return new Promise((w)=>{let g=xe(m,p,{cwd:r,stdio:["ignore","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}}),h="";if(g.stdout)g.stdout.on("data",(l)=>h+=l.toString());if(g.stderr)g.stderr.on("data",(l)=>h+=l.toString());g.on("close",async(l)=>{let S=se(h,l||0);if(S.success&&s.id){let y=await k();if(!y.exercises[s.id])y.exercises[s.id]={status:"pass",xpEarned:20,completedAt:new Date().toISOString()},y.stats.totalXp+=20,y.stats=F(y.stats),await $(y)}w(Response.json({success:S.success,output:S.output||"No output",friendlyOutput:S.friendlyOutput}))}),g.on("error",(l)=>w(Response.json({success:!1,output:l.message})))})}catch(s){return Response.json({success:!1,output:String(s)})}},ne={"/api/exercises":{GET:Oe},"/api/exercises/quiz":{GET:Ne},"/api/exercises/code":{GET:je},"/api/exercises/run":{POST:Ce}};import{readFile as Te,exists as ve}from"fs/promises";import{join as ke}from"path";var Ee=async()=>{if(await R(),!u||!u.setup)return Response.json({success:!0,checks:[]});let e=await ee(u.setup);return Response.json({success:e.every((s)=>s.status==="pass"),checks:e})},Ie=async()=>{if(await R(),!u||!u.setup?.guide)return Response.json({markdown:"# No setup guide available"});let e=ke(j,u.setup.guide);if(await ve(e)){let s=await Te(e,"utf-8");return Response.json({markdown:s})}return Response.json({markdown:"# Setup guide not found"})},oe={"/api/setup/status":{GET:Ee},"/api/setup/guide":{GET:Ie}};var Ae=async()=>{return Response.json({hint:"Thinking..."})},ie={"/api/ai/hint":{POST:Ae}};import{spawn as _e}from"child_process";var $e=async(e)=>{try{let{path:s}=await e.json();if(!s)return Response.json({success:!1,error:"Missing path"});console.log(`[IDE] Opening ${s} in VS Code...`);let t=_e("code",[s],{shell:!0});return new Promise((n)=>{t.on("error",(o)=>{console.error(`[IDE] Failed to spawn 'code': ${o}`),n(Response.json({success:!1,error:"VS Code not found in PATH"}))}),t.on("spawn",()=>{n(Response.json({success:!0}))})})}catch(s){return console.error(`[IDE] Failed to open: ${s}`),Response.json({success:!1,error:String(s)},{status:500})}},ae={"/api/ide/open":{POST:$e}};var De=import.meta.file.endsWith(".ts"),H=G(import.meta.dir,De?"../../public":"../public");console.log("[INFO] Server starting...");var Ge=Fe({port:3001,routes:{"/":()=>new Response(Bun.file(G(H,"index.html"))),"/main.js":()=>new Response(Bun.file(G(H,"main.js"))),"/main.css":()=>new Response(Bun.file(G(H,"main.css"))),...J,...te,...re,...ne,...oe,...ie,...ae},development:{hmr:process.env.ENABLE_HMR==="true"},fetch(e){return new Response("Not Found",{status:404})}});console.log(`\uD83D\uDE80 Progy Server running on ${Ge.url}`);
28
+ `)+e}}var Pe=async()=>{return Response.json(await E())},Oe=async(e)=>{try{let{type:t,id:s,success:n}=await e.json();if(!s)return Response.json({success:!1,error:"Missing ID"});let o=await E(),c=new Date().toISOString();if(t==="quiz"&&n){if(!o.quizzes[s])o.quizzes[s]={passed:!0,xpEarned:10,completedAt:c},o.stats.totalXp+=10,o.stats=G(o.stats),await D(o)}return Response.json({success:!0,progress:o})}catch(t){return Response.json({success:!1,error:String(t)})}},ne={"/api/progress":{GET:Pe},"/api/progress/update":{POST:Oe}};var Ne=async()=>{return await R(),Response.json({...u||{},remoteApiUrl:process.env.PROGY_API_URL||"https://progy.francy.workers.dev"})},oe={"/api/config":{GET:Ne}};import{readFile as L,exists as B}from"fs/promises";import{join as U}from"path";import{spawn as Te}from"child_process";var je=async()=>{if(await R(),!u)return Response.json({error:"No config"});let e=await te(u);return Response.json(Array.isArray(e)?{}:e)},ve=async(e)=>{let s=new URL(e.url).searchParams.get("path");if(!s)return new Response("Missing path",{status:400});let n=U(s,"quiz.json");try{if(await B(n)){let o=await L(n,"utf-8");return Response.json(JSON.parse(o))}return Response.json({error:"Quiz not found"},{status:404})}catch(o){return Response.json({error:"Invalid quiz file"},{status:500})}},Ce=async(e)=>{let t=new URL(e.url),s=t.searchParams.get("path"),n=t.searchParams.get("markdownPath");if(!s)return new Response("Missing path",{status:400});try{let o="";if((await Bun.file(s).stat()).isDirectory()){let p=["exercise.rs","main.rs","index.ts","main.go","index.js"];for(let a of p){let r=U(s,a);if(await B(r)){o=await L(r,"utf-8");break}}if(!o)o="// No entry file found"}else o=await L(s,"utf-8");let m=null;if(n&&await B(n))m=await L(n,"utf-8");return Response.json({code:o,markdown:m})}catch(o){return Response.json({error:"File not found"},{status:404})}},ke=async(e)=>{try{await R();let t=await e.json(),{exerciseName:s,id:n}=t,c=(n?.split("/")||[])[0]||"",m=u.runner.command,p=u.runner.args.map((w)=>w.replace("{{exercise}}",s).replace("{{id}}",n||"").replace("{{module}}",c)),r=(u.runner.cwd?U(T,u.runner.cwd):T).replace("{{exercise}}",s).replace("{{id}}",n||"").replace("{{module}}",c);return new Promise((w)=>{let g=Te(m,p,{cwd:r,stdio:["ignore","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}}),h="";if(g.stdout)g.stdout.on("data",(l)=>h+=l.toString());if(g.stderr)g.stderr.on("data",(l)=>h+=l.toString());g.on("close",async(l)=>{let S=re(h,l||0);if(S.success&&t.id){let y=await E();if(!y.exercises[t.id])y.exercises[t.id]={status:"pass",xpEarned:20,completedAt:new Date().toISOString()},y.stats.totalXp+=20,y.stats=G(y.stats),await D(y)}w(Response.json({success:S.success,output:S.output||"No output",friendlyOutput:S.friendlyOutput}))}),g.on("error",(l)=>w(Response.json({success:!1,output:l.message})))})}catch(t){return Response.json({success:!1,output:String(t)})}},ae={"/api/exercises":{GET:je},"/api/exercises/quiz":{GET:ve},"/api/exercises/code":{GET:Ce},"/api/exercises/run":{POST:ke}};import{readFile as Ee,exists as Ie}from"fs/promises";import{join as Ae}from"path";var _e=async()=>{if(await R(),!u||!u.setup)return Response.json({success:!0,checks:[]});let e=await se(u.setup);return Response.json({success:e.every((t)=>t.status==="pass"),checks:e})},$e=async()=>{if(await R(),!u||!u.setup?.guide)return Response.json({markdown:"# No setup guide available"});let e=Ae(T,u.setup.guide);if(await Ie(e)){let t=await Ee(e,"utf-8");return Response.json({markdown:t})}return Response.json({markdown:"# Setup guide not found"})},ie={"/api/setup/status":{GET:_e},"/api/setup/guide":{GET:$e}};var Fe=async()=>{return Response.json({hint:"Thinking..."})},ce={"/api/ai/hint":{POST:Fe}};import{spawn as De}from"child_process";var Ge=async(e)=>{try{let{path:t}=await e.json();if(!t)return Response.json({success:!1,error:"Missing path"});console.log(`[IDE] Opening ${t} in VS Code...`);let s=De("code",[t],{shell:!0});return new Promise((n)=>{s.on("error",(o)=>{console.error(`[IDE] Failed to spawn 'code': ${o}`),n(Response.json({success:!1,error:"VS Code not found in PATH"}))}),s.on("spawn",()=>{n(Response.json({success:!0}))})})}catch(t){return console.error(`[IDE] Failed to open: ${t}`),Response.json({success:!1,error:String(t)},{status:500})}},ue={"/api/ide/open":{POST:Ge}};var Le=async()=>{let e=await v();return Response.json({token:e?.token||null})},be=async()=>{return await F({token:null}),Response.json({success:!0})},pe={"/api/auth/token":{GET:Le,POST:be}};var ze=async()=>{let e=await v(),{token:t,...s}=e||{};return Response.json(s)},Me=async(e)=>{let t=await e.json();return await F(t),Response.json({success:!0})},le={"/api/local-settings":{GET:ze,POST:Me}};var We=import.meta.file.endsWith(".ts"),q=b(import.meta.dir,We?"../../public":"../public");console.log("[INFO] Server starting...");var Je=He({port:3001,routes:{"/":()=>new Response(Bun.file(b(q,"index.html"))),"/main.js":()=>new Response(Bun.file(b(q,"main.js"))),"/main.css":()=>new Response(Bun.file(b(q,"main.css"))),...Y,...ne,...oe,...ae,...ie,...ce,...ue,...pe,...le},development:{hmr:process.env.ENABLE_HMR==="true"},fetch(e){return new Response("Not Found",{status:404})}});console.log(`\uD83D\uDE80 Progy Server running on ${Je.url}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progy",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "The interactive CLI and learning platform for Progy courses.",
5
5
  "license": "MIT",
6
6
  "repository": {