devcoach 2.8.0-next.711.gc433a9b → 2.8.0-next.712.gda0b1a4
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.
- package/dist/{app-HWYWSZWB.js → app-YX6LSJM2.js} +4 -4
- package/dist/bin.js +1 -1
- package/dist/{chunk-3EZR3N7Z.js → chunk-DYT5EZOC.js} +1 -1
- package/dist/{chunk-LL4ALHOT.js → chunk-ENZJ5CJ3.js} +3 -3
- package/dist/chunk-ILDBPNYJ.js +2 -0
- package/dist/{chunk-YC2E6UKL.js → chunk-K3BUZMZU.js} +1 -1
- package/dist/chunk-RGZ6DF6G.js +3 -0
- package/dist/chunk-SDIK7HUX.js +118 -0
- package/dist/commands-DGTJLMG3.js +37 -0
- package/dist/{hooks-QEY2BALR.js → hooks-5X2QMD4I.js} +1 -1
- package/dist/{server-AFPXM6R3.js → server-FILCKV5R.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-BNWC4YUO.js +0 -2
- package/dist/chunk-QU6UQJWC.js +0 -3
- package/dist/chunk-WMTRKUYW.js +0 -118
- package/dist/commands-IBQPXHRE.js +0 -37
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {a as a$3,
|
|
2
|
+
import {a as a$3,e as e$1}from'./chunk-RGZ6DF6G.js';import {p,q,h as h$1,m,a as a$1,e,g,j as j$1,l,n}from'./chunk-K3BUZMZU.js';import {z,E as E$1,A,y,G,X as X$1,Ea,Ia,Fa,R,V as V$1,S,W as W$1,Ga,T,U,L,Ja,ra,va,N,M,P as P$1,H,K as K$1,Ha,J as J$1,O,Y as Y$1,w,Aa,za,b as b$1,c,a as a$2,oa,qa,r,ta,sa,ua,xa,wa}from'./chunk-SDIK7HUX.js';import {a}from'./chunk-ILDBPNYJ.js';import {existsSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {dirname,join,extname}from'path';import {fileURLToPath}from'url';import {serve}from'@hono/node-server';import {Hono}from'hono';import {html,raw}from'hono/html';function j(r){return JSON.stringify(r??"").replace(/[<>&\u2028\u2029]/g,e=>String.raw`\u`+(e.codePointAt(0)??0).toString(16).padStart(4,"0"))}function We(r,e){return r+e.replace(/[^A-Za-z0-9_-]/g,g=>`_${(g.codePointAt(0)??0).toString(16)}`)}var lt=html`<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><circle cx="4" cy="10" r="1.75"/><circle cx="10" cy="10" r="1.75"/><circle cx="16" cy="10" r="1.75"/></svg>`;function Y(r,e="",g=""){return html`<div class="relative ${e}" ${raw(g)} x-data="{ open: false }" @click.outside="open = false" @keydown.escape="open = false">
|
|
3
3
|
<button type="button" @click="open = !open" :aria-expanded="open" aria-label="More actions" title="More actions" class="w-8 h-8 inline-flex items-center justify-center rounded-lg border transition bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:border-accent-400 hover:text-gray-800 dark:hover:text-gray-100">${lt}</button>
|
|
4
4
|
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100" class="absolute right-0 top-full mt-1 z-50 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg w-56 p-1" style="display:none">
|
|
5
5
|
${r}
|
|
@@ -862,10 +862,10 @@ function updateLabel(inputId, labelId, submitId) {
|
|
|
862
862
|
}
|
|
863
863
|
marked.setOptions({ breaks: true, gfm: true });
|
|
864
864
|
document.getElementById('notebook-preview-settings').innerHTML = DOMPurify.sanitize(marked.parse(${raw(j(r.notebookContent))}));
|
|
865
|
-
</script>`;return P({title:"Settings \u2014 devcoach",currentPath:"/settings",uiTheme:r.uiTheme,body:a,scripts:n})}var Z=25;function B(r,e="/lessons"){return r?.startsWith("/")&&!r.startsWith("//")?r:e}function b(r,e,g=""){let d=r[e];return typeof d=="string"?d:g}var Q=dirname(fileURLToPath(import.meta.url)),X=[join(Q,"../assets/static"),join(Q,"../../assets/static")].find(r=>existsSync(r))??join(Q,"../assets/static"),kt={".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".woff2":"font/woff2",".woff":"font/woff",".ico":"image/x-icon"},vt="https://ultimaphoenix.github.io",nt={"access-control-allow-origin":vt,"access-control-allow-methods":"GET, OPTIONS","access-control-allow-private-network":"true","cache-control":"no-store",vary:"Origin"};function E(r){let e=r.req.header("sec-fetch-site");return e!==void 0&&e!=="same-origin"&&e!=="none"}var ee;function wt(){return ee===void 0&&(ee=n()),ee}function it(r,e,g){let d=ta({explicit:g.name??null,setting:e,gitUserName:wt()}),t=g.includeContext??false,a=sa(r,{includeContext:t,sharedBy:d}),n=ua(a);return {payload:a,state:{id:r.id,open:g.open??false,name:d??"",includeContext:t,text:wa(a,n),link:xa(a,n)}}}function $t(r,e){return r==="lessons"?"/lessons":r==="knowledge"?"/knowledge":e?"/lessons":"/knowledge"}function _t(){try{return E$1(r=>X$1(r).ui_theme)}catch{return "system"}}function St(r$1={}){let e$1=new Hono;e$1.onError((t,a)=>t instanceof z?a.html(at(t.message),503):(console.error(t),a.text("Internal Server Error",500))),e$1.get("/static/*",t=>{let a=decodeURIComponent(t.req.path.slice(8)),n=join(X,a);if(!n.startsWith(X))return t.notFound();try{let i=readFileSync(n);return new Response(new Uint8Array(i),{headers:{"content-type":kt[extname(n)]??"application/octet-stream","cache-control":"no-cache"}})}catch{return t.notFound()}}),e$1.get("/ping",t=>{let a$1=null;try{a$1=E$1(n=>{let i=A(n);return i?{...i,message:y(i)}:null});}catch{}return t.json({ok:true,version:a,compat:a$1},200,nt)}),e$1.options("/ping",t=>t.body(null,204,nt)),e$1.post("/shutdown",t=>E(t)?t.text("Forbidden",403):(setTimeout(()=>r$1.onShutdown?.(),50),t.json({ok:true}))),e$1.get("/shutdown",t=>t.text("Method Not Allowed",405)),e$1.get("/",t=>{let{home:a,hasLessons:n}=E$1(i=>({home:X$1(i).ui_home,hasLessons:G(i)>0}));return t.redirect($t(a,n),302)}),e$1.get("/knowledge",t=>{let{profile:a,stats:n,rateLimit:i,settings:s}=E$1(p=>({profile:
|
|
865
|
+
</script>`;return P({title:"Settings \u2014 devcoach",currentPath:"/settings",uiTheme:r.uiTheme,body:a,scripts:n})}var Z=25;function B(r,e="/lessons"){return r?.startsWith("/")&&!r.startsWith("//")?r:e}function b(r,e,g=""){let d=r[e];return typeof d=="string"?d:g}var Q=dirname(fileURLToPath(import.meta.url)),X=[join(Q,"../assets/static"),join(Q,"../../assets/static")].find(r=>existsSync(r))??join(Q,"../assets/static"),kt={".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".woff2":"font/woff2",".woff":"font/woff",".ico":"image/x-icon"},vt="https://ultimaphoenix.github.io",nt={"access-control-allow-origin":vt,"access-control-allow-methods":"GET, OPTIONS","access-control-allow-private-network":"true","cache-control":"no-store",vary:"Origin"};function E(r){let e=r.req.header("sec-fetch-site");return e!==void 0&&e!=="same-origin"&&e!=="none"}var ee;function wt(){return ee===void 0&&(ee=n()),ee}function it(r,e,g){let d=ta({explicit:g.name??null,setting:e,gitUserName:wt()}),t=g.includeContext??false,a=sa(r,{includeContext:t,sharedBy:d}),n=ua(a);return {payload:a,state:{id:r.id,open:g.open??false,name:d??"",includeContext:t,text:wa(a,n),link:xa(a,n)}}}function $t(r,e){return r==="lessons"?"/lessons":r==="knowledge"?"/knowledge":e?"/lessons":"/knowledge"}function _t(){try{return E$1(r=>X$1(r).ui_theme)}catch{return "system"}}function St(r$1={}){let e$1=new Hono;e$1.onError((t,a)=>t instanceof z?a.html(at(t.message),503):(console.error(t),a.text("Internal Server Error",500))),e$1.get("/static/*",t=>{let a=decodeURIComponent(t.req.path.slice(8)),n=join(X,a);if(!n.startsWith(X))return t.notFound();try{let i=readFileSync(n);return new Response(new Uint8Array(i),{headers:{"content-type":kt[extname(n)]??"application/octet-stream","cache-control":"no-cache"}})}catch{return t.notFound()}}),e$1.get("/ping",t=>{let a$1=null;try{a$1=E$1(n=>{let i=A(n);return i?{...i,message:y(i)}:null});}catch{}return t.json({ok:true,version:a,compat:a$1},200,nt)}),e$1.options("/ping",t=>t.body(null,204,nt)),e$1.post("/shutdown",t=>E(t)?t.text("Forbidden",403):(setTimeout(()=>r$1.onShutdown?.(),50),t.json({ok:true}))),e$1.get("/shutdown",t=>t.text("Method Not Allowed",405)),e$1.get("/",t=>{let{home:a,hasLessons:n}=E$1(i=>({home:X$1(i).ui_home,hasLessons:G(i)>0}));return t.redirect($t(a,n),302)}),e$1.get("/knowledge",t=>{let{profile:a,stats:n,rateLimit:i,settings:s}=E$1(p=>({profile:Fa(p),stats:Ia(p),rateLimit:Ea(p),settings:X$1(p)})),c=new Map;for(let p of a.groups)for(let m of p.topics)c.set(m,p.name);let u={};for(let p of a.groups)u[p.name]=[];for(let p of a.knowledge){let m=c.get(p.topic)??"Other",v=u[m]??[];v.push(p),u[m]=v;}return t.html(Ze({categorised:u,allGroups:a.groups.map(p=>p.name),stats:n,rateLimit:i,maxPerDay:s.max_per_day,uiTheme:s.ui_theme}))}),e$1.post("/knowledge",async t=>{let a=await t.req.parseBody(),n=b(a,"topic").trim();if(n){let i=Number.parseInt(b(a,"confidence","5"),10)||5,s=b(a,"group").trim();E$1(c=>{R(c,n,i),s&&s!=="Other"&&V$1(c,n,s);});}return t.redirect("/knowledge",303)}),e$1.post("/knowledge/:topic/delete",t=>(E$1(a=>S(a,t.req.param("topic"))),t.redirect("/knowledge",303))),e$1.post("/knowledge/:topic/group",async t=>{let a=b(await t.req.parseBody(),"group").trim(),n=t.req.param("topic");return E$1(i=>{a&&a!=="Other"?V$1(i,n,a):W$1(i,n);}),t.redirect("/knowledge",303)}),e$1.post("/knowledge/:topic",async t=>{let a=Number.parseInt(b(await t.req.parseBody(),"delta","0"),10)||0;return E$1(n=>Ga(n,t.req.param("topic"),a)),t.redirect("/knowledge",303)}),e$1.post("/groups",async t=>{let a=b(await t.req.parseBody(),"group_name").trim();return a&&a!=="Other"&&E$1(n=>T(n,a)),t.redirect("/knowledge",303)}),e$1.post("/groups/:group_name/delete",t=>(E$1(a=>U(a,t.req.param("group_name"))),t.redirect("/knowledge",303))),e$1.get("/lessons/export",()=>{let t=E$1(a=>L(a));return new Response(JSON.stringify(t,null,2),{headers:{"content-type":"application/json","content-disposition":"attachment; filename=devcoach-lessons.json"}})}),e$1.post("/lessons/import",async t=>{let a=await t.req.parseBody(),n=b(a,"from")==="lessons",i=p=>t.redirect(n?`/lessons?import=1&error=${p}`:"/settings?imported=0&skipped=0&invalid=1",303);if(E(t))return i("cross");let s=a.file,u=(s instanceof File&&s.size>0?await s.text():null)??b(a,"text").trim();try{p(u)&&!u.includes("#devcoach:lesson:")&&(u=await q(u));let p$1=E$1(m=>Ja(m,u));return p$1.kind==="lessons"||!p$1.lesson?t.redirect(`/settings?imported=${p$1.inserted}&skipped=${p$1.duplicated}&invalid=${p$1.invalid}`,303):t.redirect(`/lessons/${encodeURIComponent(p$1.lesson.id)}?imported=${p$1.inserted?"1":"dup"}`,303)}catch(p){if(p instanceof ra)return i("invalid");throw p}}),e$1.get("/lessons/import",t=>{let a=(t.req.query("code")??"").trim(),n=null,i=null;if(a)try{n=va(a);}catch(s){i=s instanceof ra?s.message:"That code could not be read.";}return t.html(et({code:a,preview:n,error:i,uiTheme:_t()}))}),e$1.get("/lessons",t=>{let a=t.req.query(),n=new Set(["junior","mid","senior"]),i=a.level&&n.has(a.level)?a.level:"",s=a.date_from||"",c=a.date_to||"",u=a.starred==="1",p=a.imported==="1"?"1":a.imported==="0"?"0":"",m=a.shared_by||null,v=a.sort||"timestamp",T=a.order==="asc"?"asc":"desc",R=Math.max(1,Number.parseInt(a.page??"1",10)||1),L=a.period||"all",l={period:s||c?null:L!=="all"?L:null,category:a.category||null,level:i||null,project:a.project||null,repository:a.repository||null,branch:a.branch||null,commit:a.commit||null,starred:u?true:null,imported:m||p==="1"?true:p==="0"?false:null,shared_by:m,search:a.search||null,feedback:a.feedback||null,date_from:s||null,date_to:c||null},x=E$1(k=>{let re=G(k,l),ae=Math.max(1,Math.ceil(re/Z));return R=Math.min(R,ae),{total:re,totalPages:ae,lessons:H(k,{...l,sort:v,order:T,page:R,per_page:Z}),allCategories:P$1(k),allProjects:M(k,"project"),allRepositories:M(k,"repository"),allBranches:M(k,"branch"),allCommits:M(k,"commit_hash"),allSharedBy:N(k),theme:X$1(k).ui_theme}}),$={period:L,category:a.category||"",level:i,project:a.project||"",repository:a.repository||"",branch:a.branch||"",commit:a.commit||"",starred:u,imported:m?"1":p,shared_by:m??"",search:a.search||"",feedback:a.feedback||"",date_from:s,date_to:c,sort:v,order:T},S=a.error==="invalid"||a.error==="cross"?a.error:null;return t.html(Qe({importOpen:a.import==="1",importError:S,lessons:x.lessons,allCategories:x.allCategories,allProjects:x.allProjects,allRepositories:x.allRepositories,allBranches:x.allBranches,allCommits:x.allCommits,allSharedBy:x.allSharedBy,s:$,page:R,perPage:Z,total:x.total,totalPages:x.totalPages,uiTheme:x.theme}))}),e$1.post("/lessons/:lesson_id/star",async t=>{let a=await t.req.parseBody();return E$1(n=>K$1(n,t.req.param("lesson_id"),a.starred==="1")),t.redirect(B(b(a,"next")||void 0),303)}),e$1.post("/lessons/:lesson_id/feedback",async t=>{let a=await t.req.parseBody(),n=b(a,"feedback");if(!["","clear","know","understood","dont_know"].includes(n))return t.text("Unknown feedback value",400);let i=n===""||n==="clear"?null:n;return E$1(s=>Ha(s,t.req.param("lesson_id"),i)),t.redirect(B(b(a,"next")||void 0),303)}),e$1.post("/lessons/delete",async t=>{if(E(t))return t.text("Forbidden",403);let a=await t.req.parseBody({all:true}),n=a.id,i=(Array.isArray(n)?n:[n]).filter(s=>typeof s=="string"&&s!=="");return E$1(s=>J$1(s,i)),t.redirect(B(b(a,"next")||void 0),303)}),e$1.on(["GET","POST"],"/lessons/:lesson_id/share",async t=>{if(E(t))return t.text("Forbidden",403);let a,n,i=false;if(t.req.method==="POST"){let m=await t.req.parseBody();a=b(m,"name"),n=b(m,"include_context")==="1",i=b(m,"persist")==="1";}else a=t.req.query("name"),n=t.req.query("include_context")==="1";let s=E$1(m=>{let v=O(m,t.req.param("lesson_id")??"");return v?(i&&a!==void 0&&Y$1(m,"share_name",w(a)),{lesson:v,settingName:X$1(m).share_name}):null});if(!s)return t.text("Lesson not found",404);let{state:c,payload:u}=it(s.lesson,s.settingName,{name:a,includeContext:n}),p=t.req.query("format");return p==="text"?t.text(c.text):p==="link"?t.text(c.link):p==="md"?new Response(Aa(u),{headers:{"content-type":"text/markdown; charset=utf-8","content-disposition":`attachment; filename="${za(u)}"`}}):p==="panel"?t.html(W(s.lesson.id,c,s.lesson.title)):t.html(V(c))}),e$1.get("/lessons/:lesson_id",t=>{let a=E$1(s=>{let c=O(s,t.req.param("lesson_id"));if(!c)return null;let u=X$1(s),p=h$1(s,{lesson_id:c.id})[0],m$1=p?{id:p.id,title:p.title,status:p.status,...m(p)}:null;return {lesson:c,course:m$1,settingName:u.share_name,uiTheme:u.ui_theme}});if(!a)return t.html("<h1>Lesson not found</h1>",404);let n=t.req.query(),i=n.imported==="1"?"new":n.imported==="dup"?"dup":null;return t.html(Xe({lesson:a.lesson,uiTheme:a.uiTheme,share:it(a.lesson,a.settingName,{open:n.share==="1"}).state,imported:i,course:a.course}))});let g$1={"content-type":"text/html; charset=utf-8","content-security-policy":"sandbox allow-scripts allow-forms; default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; style-src 'unsafe-inline'; img-src data:; font-src data:; form-action 'none'; base-uri 'none'; frame-ancestors 'self'","referrer-policy":"no-referrer","x-content-type-options":"nosniff","cache-control":"no-cache"},d=(()=>{try{return `
|
|
866
866
|
<script>${readFileSync(join(X,"course-frame.js"),"utf8")}</script>
|
|
867
867
|
`}catch{return ""}})();return e$1.get("/courses",t=>{let a=E$1(n=>({courses:h$1(n).map(i=>({...i,lesson_title:i.lesson_id?O(n,i.lesson_id)?.title??null:null})),uiTheme:X$1(n).ui_theme}));return t.html(rt(a))}),e$1.get("/courses/:course_id/index.html",t=>{let a=t.req.param("course_id");if(!a$1.test(a))return t.notFound();let n=e(a);if(!n)return t.notFound();try{let i=readFileSync(n),s=i.toString("utf8"),c$1=!b$1(s)&&c(s)?`
|
|
868
|
-
${a$2()??""}`:"",u=Buffer.concat([i,Buffer.from(d+c$1)]);return new Response(new Uint8Array(u),{headers:new Headers(g$1)})}catch{return t.notFound()}}),e$1.get("/courses/:course_id",t=>{let a=t.req.param("course_id"),n=E$1(c=>{let u=g(c,a);return u?{course:u,lessonTitle:u.lesson_id?O(c,u.lesson_id)?.title??null:null,uiTheme:X$1(c).ui_theme}:null});if(!n)return t.html("<h1>Course not found</h1>",404);let i=n.course.steps.find(c=>c.status==="todo")?.position??1,s=Number.parseInt(t.req.query("step")??"",10)||i;return t.html(ot({...n,selected:s,hasDocument:e(a)!==null}))}),e$1.post("/courses/:course_id/steps/:position",async t=>{if(E(t))return t.text("Forbidden",403);let a=await t.req.parseBody(),n=b(a,"status");if(!["todo","done","skipped"].includes(n))return t.text("Unknown status",400);let i=Number.parseInt(t.req.param("position"),10),s=t.req.param("course_id");return E$1(u=>Number.isFinite(i)?j$1(u,s,i,n):null)?t.redirect(B(b(a,"next")||void 0,`/courses/${encodeURIComponent(s)}`),303):t.text("Step not found",404)}),e$1.post("/courses/delete",async t=>{if(E(t))return t.text("Forbidden",403);let a=await t.req.parseBody();return E$1(n=>l(n,b(a,"id"))),t.redirect("/courses",303)}),e$1.get("/settings/export",()=>{let t=E$1(a=>oa(a));return new Response(new Uint8Array(t),{headers:{"content-type":"application/zip","content-disposition":"attachment; filename=devcoach-backup.zip"}})}),e$1.post("/settings/import",async t=>{let a=(await t.req.parseBody()).file;if(!(a instanceof File))return t.redirect("/settings?imported=0&skipped=0&invalid=1",303);let n=new Uint8Array(await a.arrayBuffer()),i=E$1(s=>qa(s,n));return t.redirect(`/settings?imported=${i.lessons}&skipped=${i.skipped}&invalid=${i.invalid}&groups=${i.groups}¬ebook=${i.learning_state}`,303)}),e$1.get("/settings/notebook/download",()=>{let t=existsSync(r)?readFileSync(r,"utf8"):"";return new Response(t,{headers:{"content-type":"text/markdown","content-disposition":"attachment; filename=devcoach-notebook.md"}})}),e$1.post("/settings/notebook",async t=>{let a=await t.req.parseBody();return mkdirSync(dirname(r),{recursive:true}),writeFileSync(r,b(a,"content"),"utf8"),t.redirect(B(b(a,"next")||void 0,"/settings?notebook_saved=1"),303)}),e$1.post("/settings",async t=>{let a=await t.req.parseBody(),n=Number.parseInt(b(a,"max_per_day","2"),10)||2,i=Number.parseInt(b(a,"min_gap_minutes","240"),10),s=b(a,"ui_theme","system");["system","dark","light"].includes(s)||(s="system");let c=b(a,"ui_home","auto");["auto","lessons","knowledge"].includes(c)||(c="auto");let u=Math.max(0,Number.parseInt(b(a,"nudge_every","10"),10)||0),p=b(a,"nudge_scope","session");return p!=="session"&&p!=="global"&&(p="session"),E$1(m=>{Y$1(m,"max_per_day",String(n)),Y$1(m,"min_gap_minutes",String(Number.isNaN(i)?240:i)),Y$1(m,"ui_theme",s),Y$1(m,"ui_home",c),Y$1(m,"nudge_every",String(u)),Y$1(m,"nudge_scope",p),Y$1(m,"share_name",w(b(a,"share_name")));}),t.redirect("/settings",303)}),e$1.get("/settings",t=>{let a=E$1(c=>X$1(c)),n=existsSync(r)?readFileSync(r,"utf8"):"",i=t.req.query(),s=i.imported!==void 0?{imported:Number(i.imported??0),skipped:Number(i.skipped??0),invalid:Number(i.invalid??0),groups:Number(i.groups??0),notebook:Number(i.notebook??0)}:null;return t.html(st({settings:a,notebookContent:n,notebookPath:r,uiTheme:a.ui_theme,flash:s}))}),e$1}function Ct(r,e=2e3){let g=r;return new Promise(d=>{let t=setTimeout(()=>{g.closeAllConnections?.();},e),a=setInterval(()=>g.closeIdleConnections?.(),100);r.close(()=>{clearTimeout(t),clearInterval(a),d();}),g.closeIdleConnections?.();})}async function Tt(r,e=fetch){try{let g=await e(`http://127.0.0.1:${r}/ping`,{signal:AbortSignal.timeout(1500)});if(!g.ok)return null;let d=await g.json();return d.ok===!0?{version:String(d.version??"")}:null}catch{return null}}async function It(r,e,g=Tt){if(e.code==="EADDRINUSE"){let d=await g(r);if(d){let t=`http://localhost:${r}`,a=d.version?` (v${d.version})`:"";return {existingUrl:t,exitCode:0,message:`${a$3.yellow("devcoach UI is already running at")} ${a$3.cyan(
|
|
868
|
+
${a$2()??""}`:"",u=Buffer.concat([i,Buffer.from(d+c$1)]);return new Response(new Uint8Array(u),{headers:new Headers(g$1)})}catch{return t.notFound()}}),e$1.get("/courses/:course_id",t=>{let a=t.req.param("course_id"),n=E$1(c=>{let u=g(c,a);return u?{course:u,lessonTitle:u.lesson_id?O(c,u.lesson_id)?.title??null:null,uiTheme:X$1(c).ui_theme}:null});if(!n)return t.html("<h1>Course not found</h1>",404);let i=n.course.steps.find(c=>c.status==="todo")?.position??1,s=Number.parseInt(t.req.query("step")??"",10)||i;return t.html(ot({...n,selected:s,hasDocument:e(a)!==null}))}),e$1.post("/courses/:course_id/steps/:position",async t=>{if(E(t))return t.text("Forbidden",403);let a=await t.req.parseBody(),n=b(a,"status");if(!["todo","done","skipped"].includes(n))return t.text("Unknown status",400);let i=Number.parseInt(t.req.param("position"),10),s=t.req.param("course_id");return E$1(u=>Number.isFinite(i)?j$1(u,s,i,n):null)?t.redirect(B(b(a,"next")||void 0,`/courses/${encodeURIComponent(s)}`),303):t.text("Step not found",404)}),e$1.post("/courses/delete",async t=>{if(E(t))return t.text("Forbidden",403);let a=await t.req.parseBody();return E$1(n=>l(n,b(a,"id"))),t.redirect("/courses",303)}),e$1.get("/settings/export",()=>{let t=E$1(a=>oa(a));return new Response(new Uint8Array(t),{headers:{"content-type":"application/zip","content-disposition":"attachment; filename=devcoach-backup.zip"}})}),e$1.post("/settings/import",async t=>{let a=(await t.req.parseBody()).file;if(!(a instanceof File))return t.redirect("/settings?imported=0&skipped=0&invalid=1",303);let n=new Uint8Array(await a.arrayBuffer()),i=E$1(s=>qa(s,n));return t.redirect(`/settings?imported=${i.lessons}&skipped=${i.skipped}&invalid=${i.invalid}&groups=${i.groups}¬ebook=${i.learning_state}`,303)}),e$1.get("/settings/notebook/download",()=>{let t=existsSync(r)?readFileSync(r,"utf8"):"";return new Response(t,{headers:{"content-type":"text/markdown","content-disposition":"attachment; filename=devcoach-notebook.md"}})}),e$1.post("/settings/notebook",async t=>{let a=await t.req.parseBody();return mkdirSync(dirname(r),{recursive:true}),writeFileSync(r,b(a,"content"),"utf8"),t.redirect(B(b(a,"next")||void 0,"/settings?notebook_saved=1"),303)}),e$1.post("/settings",async t=>{let a=await t.req.parseBody(),n=Number.parseInt(b(a,"max_per_day","2"),10)||2,i=Number.parseInt(b(a,"min_gap_minutes","240"),10),s=b(a,"ui_theme","system");["system","dark","light"].includes(s)||(s="system");let c=b(a,"ui_home","auto");["auto","lessons","knowledge"].includes(c)||(c="auto");let u=Math.max(0,Number.parseInt(b(a,"nudge_every","10"),10)||0),p=b(a,"nudge_scope","session");return p!=="session"&&p!=="global"&&(p="session"),E$1(m=>{Y$1(m,"max_per_day",String(n)),Y$1(m,"min_gap_minutes",String(Number.isNaN(i)?240:i)),Y$1(m,"ui_theme",s),Y$1(m,"ui_home",c),Y$1(m,"nudge_every",String(u)),Y$1(m,"nudge_scope",p),Y$1(m,"share_name",w(b(a,"share_name")));}),t.redirect("/settings",303)}),e$1.get("/settings",t=>{let a=E$1(c=>X$1(c)),n=existsSync(r)?readFileSync(r,"utf8"):"",i=t.req.query(),s=i.imported!==void 0?{imported:Number(i.imported??0),skipped:Number(i.skipped??0),invalid:Number(i.invalid??0),groups:Number(i.groups??0),notebook:Number(i.notebook??0)}:null;return t.html(st({settings:a,notebookContent:n,notebookPath:r,uiTheme:a.ui_theme,flash:s}))}),e$1}function Ct(r,e=2e3){let g=r;return new Promise(d=>{let t=setTimeout(()=>{g.closeAllConnections?.();},e),a=setInterval(()=>g.closeIdleConnections?.(),100);r.close(()=>{clearTimeout(t),clearInterval(a),d();}),g.closeIdleConnections?.();})}async function Tt(r,e=fetch){try{let g=await e(`http://127.0.0.1:${r}/ping`,{signal:AbortSignal.timeout(1500)});if(!g.ok)return null;let d=await g.json();return d.ok===!0?{version:String(d.version??"")}:null}catch{return null}}async function It(r,e,g=Tt){if(e.code==="EADDRINUSE"){let d=await g(r);if(d){let t=`http://localhost:${r}`,a=d.version?` (v${d.version})`:"";return {existingUrl:t,exitCode:0,message:`${a$3.yellow("devcoach UI is already running at")} ${a$3.cyan(e$1(t))}${a}
|
|
869
869
|
${a$3.dim("Open it there, start another with --port <n>, or stop it with: devcoach ui --stop")}`}}return {exitCode:1,message:`${a$3.red(`\u2717 Port ${r} is already in use by another process`)}
|
|
870
|
-
${a$3.dim("Pick another port: devcoach ui --port <n>")}`}}return e.code==="EACCES"?{exitCode:1,message:a$3.red(`\u2717 Port ${r} is not allowed (permission denied) \u2014 use a port \u2265 1024`)}:{exitCode:1,message:a$3.red(`\u2717 Could not start the devcoach UI on port ${r}: ${e.message}`)}}function Kt(r,e={}){let g=false,d=(s,c)=>{if(g){c&&process.exit(130);return}g=true,process.stdout.isTTY&&console.log(a$3.dim(`devcoach UI stopping (${s})\u2026`)),Ct(a).then(()=>{c&&process.exit(0);});},t=St({onShutdown:()=>d("stop requested",false)}),a=serve({fetch:t.fetch,port:r,hostname:"127.0.0.1"},s=>{let c=`http://localhost:${s.port}`;console.log(`devcoach UI running at ${a$3.cyan(
|
|
870
|
+
${a$3.dim("Pick another port: devcoach ui --port <n>")}`}}return e.code==="EACCES"?{exitCode:1,message:a$3.red(`\u2717 Port ${r} is not allowed (permission denied) \u2014 use a port \u2265 1024`)}:{exitCode:1,message:a$3.red(`\u2717 Could not start the devcoach UI on port ${r}: ${e.message}`)}}function Kt(r,e={}){let g=false,d=(s,c)=>{if(g){c&&process.exit(130);return}g=true,process.stdout.isTTY&&console.log(a$3.dim(`devcoach UI stopping (${s})\u2026`)),Ct(a).then(()=>{c&&process.exit(0);});},t=St({onShutdown:()=>d("stop requested",false)}),a=serve({fetch:t.fetch,port:r,hostname:"127.0.0.1"},s=>{let c=`http://localhost:${s.port}`;console.log(`devcoach UI running at ${a$3.cyan(e$1(c))}`),process.stdout.isTTY&&console.log(a$3.dim("Press Ctrl+C to stop")),e.onReady?.(c);}),n=["SIGINT","SIGTERM","SIGHUP"],i={SIGINT:()=>d("SIGINT",true),SIGTERM:()=>d("SIGTERM",true),SIGHUP:()=>d("SIGHUP",true)};if(e.handleSignals!==false)for(let s of n)process.on(s,i[s]);return a.once("error",s=>{for(let c of n)process.off(c,i[c]);It(r,s).then(c=>{if(e.onListenError){e.onListenError(c);return}console.error(c.message),process.exit(c.exitCode);});}),a}async function Yt(r,e=fetch){try{return (await e(`http://127.0.0.1:${r}/shutdown`,{method:"POST",signal:AbortSignal.timeout(3e3)})).ok}catch{return false}}
|
|
871
871
|
export{St as createApp,It as explainListenError,Ct as gracefulShutdown,Tt as pingUi,$t as resolveHomePath,Kt as startUi,Yt as stopUi};
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {c as c$1,e as e$1,f}from'./chunk-
|
|
2
|
+
import {c as c$1,e as e$1,f}from'./chunk-DYT5EZOC.js';import'./chunk-ILDBPNYJ.js';var e=process.argv[2]??"",i=new Set(["stop-hook","prompt-hook","gemini-stop-hook","gemini-prompt-hook","codex-stop-hook","codex-prompt-hook","onboard-hook","lesson-ready"]);function c(){let o=e$1();return `devcoach ${o.version} (${f(o.where)}): `}var n=c$1(process.versions.node);n&&(i.has(e)&&process.exit(0),console.error(`${n} at ${process.execPath}. Upgrade Node, then restart.`),process.exit(1));try{if(i.has(e)){let{runHook:o}=await import('./hooks-5X2QMD4I.js');o(e);}else {let{runCli:o}=await import('./commands-DGTJLMG3.js');await o();}}catch(o){o?.name==="SchemaTooNewError"&&(console.error(o.message),process.exit(3)),console.error(c(),o),process.exit(1);}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {a as a$1}from'./chunk-
|
|
2
|
+
import {a as a$1}from'./chunk-ILDBPNYJ.js';import {realpathSync}from'fs';import {homedir}from'os';import {fileURLToPath}from'url';var a=24;function c(n,e=process.platform){let o=n.replace(/\\/g,"/");return e==="win32"?o.toLowerCase():o}var p=[["nvm",/\/\.nvm\/versions\/node\/(v?[\d.]+[^/]*)\//],["nvm-windows",/\/appdata\/roaming\/nvm\/(v?[\d.]+[^/]*)\//],["fnm",/\/fnm\/node-versions\/(v?[\d.]+[^/]*)\//],["volta",/\/\.volta\/tools\/image\/node\/(v?[\d.]+[^/]*)\//],["volta",/\/volta\/tools\/image\/node\/(v?[\d.]+[^/]*)\//],["asdf",/\/\.asdf\/installs\/nodejs\/(v?[\d.]+[^/]*)\//],["mise",/\/mise\/installs\/node\/(v?[\d.]+[^/]*)\//],["n",/\/n\/versions\/node\/(v?[\d.]+[^/]*)\//]];function d(n){for(let[e,o]of p){let r=o.exec(n);if(r?.[1])return {manager:e,boundTo:r[1].startsWith("v")?r[1]:`v${r[1]}`}}return null}function g(n){let e=n.toLowerCase();return e.includes("/.claude/plugins/data/")||e.includes("/.claude/plugins/cache/")?"claude-plugin":e.includes("/.devcoach/gemini-ext/")?"gemini-extension":e.includes("/claude extensions/")?"claude-desktop-extension":e.includes("/_npx/")?"npx":e.includes("/cellar/devcoach/")?"homebrew":e.includes("/scoop/apps/devcoach/")?"scoop":e.includes("/devcoach/app/dist/")?"windows-installer":e.includes("/node_modules/devcoach/")?"npm-global":/\/(src\/core\/runtime\.ts|src\/bin\.ts|dist\/[^/]+\.js)$/.test(e)?"source":"unknown"}function h(n,e=process.platform){let o=c(n,e),r=d(o),t=o.toLowerCase();return {channel:g(o),manager:r?.manager??(t.includes("/fnm_multishells/")?"fnm":null),boundTo:r?.boundTo??null,ephemeral:t.includes("/_npx/")||t.includes("/fnm_multishells/")}}function v(n,e=process.platform){let o=c(n,e),r=o.toLowerCase(),t=d(o);return t?t.manager:r.includes("/fnm_multishells/")?"fnm":r.includes("/.volta/")||r.includes("/volta/")?"volta":r.includes("/scoop/apps/nodejs")?"scoop":r.includes("/devcoach/node/")?"bundled":r.includes("/cellar/node")||r.includes("/opt/homebrew/")||r.includes("/linuxbrew/")||r.startsWith("/usr/local/opt/node")?"homebrew":r.startsWith("/usr/bin/")||r.startsWith("/usr/local/bin/")||r.includes("/program files/nodejs/")||r.startsWith("/snap/")?"system":"unknown"}function R(n){return Number.parseInt(n.split(".")[0]??"0",10)>=a?null:`devcoach ${a$1} needs Node ${a} or newer (it uses the built-in node:sqlite); this is Node ${n}`}function w(n,e=homedir(),o=process.platform){if(!e)return n;let r=o==="win32"?"\\":"/",t=o==="win32"?"%USERPROFILE%":"~";return o==="win32"?n.toLowerCase()===e.toLowerCase()?t:n.toLowerCase().startsWith(`${e.toLowerCase()}${r}`)?t+n.slice(e.length):n:n===e?t:n.startsWith(`${e}${r}`)?t+n.slice(e.length):n}function x(n){try{return realpathSync(n)}catch{return n}}function y(n={}){let e=n.platform??process.platform,o=n.execPath??process.execPath,r=n.codePath??x(fileURLToPath(import.meta.url)),t=/^(.*)[/\\](?:dist[/\\][^/\\]+\.js|src[/\\]core[/\\]runtime\.ts)$/.exec(r)?.[1];return {version:a$1,node:n.nodeVersion??process.versions.node,execPath:o,nodeManager:v(o,e),entry:t??r,where:h(r,e)}}function b(n){let e=[n.channel];return n.boundTo&&e.push(`bound to Node ${n.boundTo} (${n.manager})`),n.ephemeral&&e.push("temporary location"),e.join(", ")}function M(n,e,o=[],r=homedir(),t=process.platform){let i=u=>w(u,r,t);return [`devcoach ${e.version} ${n}`,`node ${e.node} via ${e.nodeManager} (${i(e.execPath)})`,`${b(e.where)} (${i(e.entry)})`,...o].join(" \xB7 ")}
|
|
3
3
|
export{a,h as b,R as c,w as d,y as e,b as f,M as g};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {q,E as E$1,A,ba,na,
|
|
2
|
+
import {q,E as E$1,A,ba,na,Ma,p,D,La}from'./chunk-SDIK7HUX.js';import {existsSync,mkdirSync,statSync,writeFileSync,appendFileSync,fstatSync,readFileSync}from'fs';import {join}from'path';function N(e){try{let o=JSON.parse(e);return {stop_hook_active:o?.stop_hook_active===!0,permission_mode:typeof o?.permission_mode=="string"?o.permission_mode:null,session_id:typeof o?.session_id=="string"?o.session_id:null}}catch{return {stop_hook_active:false,permission_mode:null,session_id:null}}}function l(){let e={stop_hook_active:false,permission_mode:null,session_id:null};try{let o=fstatSync(0);return !o.isFIFO()&&!o.isFile()&&!o.isSocket()?e:N(readFileSync(0,"utf8"))}catch{return e}}function $(e){return e.stop_hook_active||e.permission_mode==="plan"}function s(e,o,t){if(process.env.DEVCOACH_HOOK_DEBUG==="1")try{mkdirSync(p,{recursive:!0});let n=join(p,"hook.log");existsSync(n)&&statSync(n).size>256*1024&&writeFileSync(n,""),appendFileSync(n,`${new Date().toISOString()} ${e} ${(o??"-").slice(0,8)} ${t}
|
|
3
3
|
`);}catch{}}var E={claude:"block",gemini:"deny",codex:"block"};function u(e,o,t){let n={decision:E[e],reason:o};t&&(n.systemMessage=t),process.stdout.write(`${JSON.stringify(n)}
|
|
4
4
|
`),process.exit(0);}var g="devcoach: the user has no coaching profile yet. Ask them how they want to set it up and STRONGLY recommend Automatic \u2014 devcoach scans their full Claude Code history across all their projects (call get_onboarding for the detected stack and per-project evidence) so the profile reflects what they actually build. Also offer Guided (a short conversation to map topics and confidence levels) and Import (restore from a backup), but lead with Automatic; do not pick for them. After they choose, build the profile and save it by calling the devcoach `complete_onboarding` MCP tool (not a shell command) before ending your turn.";function I(e=l()){if($(e)&&process.exit(0),existsSync(q)){let o;try{o=E$1(t=>A(t)!==null||ba(t).knowledge_ready);}catch(t){s("onboard-hook",e.session_id,`error: ${t}`),process.exit(0);}o&&process.exit(0);}s("onboard-hook",e.session_id,"onboarding cue"),u("claude",g);}var a=10,w={claude:{cue:"Invoke the `devcoach` skill (Skill tool)",prime:"invoke the `devcoach` skill (Skill tool)"},gemini:{cue:"Activate the `devcoach` skill (activate_skill tool)",prime:"activate the `devcoach` skill (activate_skill tool)"},codex:{cue:"Activate the `devcoach` skill",prime:"activate the `devcoach` skill"}};function T(e,o="claude"){let t=e%a===0,n=Math.ceil(e/a)*a,i=t?`This is lesson #${e} \u2014 a notebook checkpoint (every ${a} lessons): after log_lesson returns the feedback, also read references/calibration.md in the devcoach skill and run its notebook-update step (a direct file write), recording "(updated after ${e} lessons)".`:`Skip the notebook update this time \u2014 it is refreshed only every ${a} lessons (next checkpoint at lesson #${n}).`;return `devcoach: a lesson is due for the technical work just completed. Do it now \u2014 do not acknowledge this message and do not explain what you are about to do.
|
|
5
5
|
|
|
@@ -8,8 +8,8 @@ ${i}
|
|
|
8
8
|
|
|
9
9
|
If the devcoach skill is not available, fall back to: call get_briefing (one call: profile, taught topics, notebook); pick ONE untaught profile topic at or above the user's confidence band; call log_lesson silently (body = 3\u20136 short paragraphs tied to the task plus a \`\u{1F4A1} *Senior tip:*\` line, clean markdown without bands or a title line); THEN write the card as the FINAL message of the turn: plain markdown (no '>' blockquote) between two band headings \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u{1F393} devcoach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\` and \`### \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 [topic] \xB7 [level] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\`, with \`**[Title]** \xB7 [Category] \xB7 [Level]\`, the body you passed, and the tip \u2014 saving is not showing; the card in the final message is the only thing the user sees. If instead the work does not warrant a lesson (pure questions, chat, nothing technical), call the devcoach \`skip_lesson\` tool with a one-line reason.
|
|
10
10
|
|
|
11
|
-
The rate-limit check is already done by this hook \u2014 skip it. Work in silence: never narrate devcoach tool calls or resource reads ('checking your profile\u2026'). Output only the lesson card (or nothing). No preamble, no meta-commentary.`}function R(e,o,t){let n=o.permission_mode==="plan";if(o.stop_hook_active)return {kind:"silent",note:"hook-forced continuation"};if(A(e))return {kind:"silent",note:"read-only: database newer than this build"};if(!ba(e).knowledge_ready)return !t.onboardCue||n?{kind:"silent",note:"onboarding not complete"}:{kind:"onboard",note:"onboarding cue (empty knowledge)"};if(na(e))return {kind:"silent",note:"course in progress \u2014 cues paused"};let i=
|
|
11
|
+
The rate-limit check is already done by this hook \u2014 skip it. Work in silence: never narrate devcoach tool calls or resource reads ('checking your profile\u2026'). Output only the lesson card (or nothing). No preamble, no meta-commentary.`}function R(e,o,t){let n=o.permission_mode==="plan";if(o.stop_hook_active)return {kind:"silent",note:"hook-forced continuation"};if(A(e))return {kind:"silent",note:"read-only: database newer than this build"};if(!ba(e).knowledge_ready)return !t.onboardCue||n?{kind:"silent",note:"onboarding not complete"}:{kind:"onboard",note:"onboarding cue (empty knowledge)"};if(na(e))return {kind:"silent",note:"course in progress \u2014 cues paused"};let i=La(e,o.session_id,{planMode:n});return i.cue?{kind:"cue",nextLessonNumber:i.nextLessonNumber,note:i.reason}:{kind:"silent",note:i.reason}}function C(e,o,t){let n;try{n=E$1(i=>D(i,()=>R(i,o,t)));}catch(i){s(e,o.session_id,`error: ${i}`),process.exit(0);}switch(s(e,o.session_id,n.note),n.kind){case "onboard":u(t.client,g);break;case "cue":u(t.client,T(n.nextLessonNumber,t.client),"\u{1F393} devcoach: checking whether a lesson is due\u2026");break;}process.exit(0);}function B(e=l()){existsSync(q)||process.exit(0),C("lesson-ready",e,{onboardCue:false,client:"claude"});}function f(e=l(),o="claude"){let t=o==="claude"?"stop-hook":`${o}-stop-hook`;existsSync(q)||((e.stop_hook_active||e.permission_mode==="plan")&&process.exit(0),s(t,e.session_id,"onboarding cue (no DB)"),u(o,g)),C(t,e,{onboardCue:true,client:o});}function L(e="claude"){return `devcoach: the lesson pacing threshold will be reached at the end of this turn. Resolve it before finishing:
|
|
12
12
|
- If this task is technical: ${w[e].prime} and follow it exactly to close your reply \u2014 the skill is the single source of truth for the entire lesson flow.
|
|
13
13
|
- If it is not technical: silently call the devcoach \`skip_lesson\` tool with a one-line reason and output nothing about it.
|
|
14
|
-
- Either way, work in silence: never narrate devcoach tool calls or resource reads ('checking your profile\u2026').`}var F={claude:"UserPromptSubmit",gemini:"BeforeAgent",codex:"UserPromptSubmit"};function b(e=l(),o="claude"){let t=o==="claude"?"prompt-hook":`${o}-prompt-hook`;e.permission_mode==="plan"&&process.exit(0),existsSync(q)||process.exit(0);let n;try{n=E$1(i=>A(i)===null&&!na(i)&&
|
|
14
|
+
- Either way, work in silence: never narrate devcoach tool calls or resource reads ('checking your profile\u2026').`}var F={claude:"UserPromptSubmit",gemini:"BeforeAgent",codex:"UserPromptSubmit"};function b(e=l(),o="claude"){let t=o==="claude"?"prompt-hook":`${o}-prompt-hook`;e.permission_mode==="plan"&&process.exit(0),existsSync(q)||process.exit(0);let n;try{n=E$1(i=>A(i)===null&&!na(i)&&Ma(i,e.session_id).wouldCue);}catch(i){s(t,e.session_id,`error: ${i}`),process.exit(0);}s(t,e.session_id,n?"priming":"no prime"),n||process.exit(0),process.stdout.write(`${JSON.stringify({hookSpecificOutput:{hookEventName:F[o],additionalContext:L(o)}})}
|
|
15
15
|
`),process.exit(0);}function K(e,o){let t=()=>o??l();switch(e){case "stop-hook":f(t());break;case "gemini-stop-hook":f(t(),"gemini");break;case "codex-stop-hook":f(t(),"codex");break;case "prompt-hook":b(t());break;case "gemini-prompt-hook":b(t(),"gemini");break;case "codex-prompt-hook":b(t(),"codex");break;case "onboard-hook":I(t());break;case "lesson-ready":B(t());break;default:process.exit(0);}}export{N as a,I as b,T as c,B as d,f as e,L as f,b as g,K as h};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {s,t,pa,da,ea,ga,fa,D,ha,ia,la,ja,ka,ma,ra,ya,d}from'./chunk-
|
|
2
|
+
import {s,t,pa,da,ea,ga,fa,D,ha,ia,la,ja,ka,ma,ra,ya,d}from'./chunk-SDIK7HUX.js';import {a}from'./chunk-ILDBPNYJ.js';import {lstatSync,mkdirSync,readFileSync,rmSync,existsSync,writeFileSync}from'fs';import {join,sep,basename}from'path';import {execFileSync}from'child_process';import st from'dns';import it from'http';import at from'https';import {isIP}from'net';var w=/^[a-z0-9-]+$/,I=t,h=class extends Error{};function tt(e){if(!w.test(e))throw new h(`Invalid course id '${e}'`)}function y(e){return tt(e),join(s,e)}function W(e){return join(y(e),I)}function B(e){let t=W(e);if(!t.startsWith(y(e)+sep))return null;try{let r=lstatSync(t);return !r.isFile()||r.size>pa?null:t}catch{return null}}function H(e){try{let t=B(e);if(t===null)return !1;let r=d(readFileSync(t,"utf8"));return r===null||Buffer.byteLength(r)>pa?!1:(writeFileSync(t,r),!0)}catch{return false}}function et(e,t){let r=ya(t,"course"),n=r;for(let o=2;ea(e,n)!==null||existsSync(join(s,n));o++)n=`${r}-${o}`;return n}function Ct(e,t){let r=new Date().toISOString(),n={id:et(e,t.title),lesson_id:t.lesson_id??null,topic_id:t.topic_id,title:t.title,goal:t.goal??null,prerequisites:t.prerequisites??[],status:"active",created_at:r,updated_at:r,completed_at:null};return da(e,n),mkdirSync(y(n.id),{recursive:true}),{...n,steps:[]}}function E(e,t){if(!w.test(t))return null;let r=ea(e,t);return r?{...r,steps:ga(e,t)}:null}function bt(e,t={}){return fa(e,t).map(r=>({...r,steps:ga(e,r.id)}))}function xt(e,t,r){return D(e,()=>{let n=E(e,t);if(!n)throw new h(`Course '${t}' not found`);let o=n.steps.length,i=r.after??o;if(!Number.isInteger(i)||i<0||i>o)throw new h(`'after' must be between 0 and ${o} (the course has ${o} steps)`);let a=B(t);if(!a)throw new h(`Course '${t}' has no document yet \u2014 write ${W(t)} first`);let f=readFileSync(a,"utf8");if(!new RegExp(`\\sid=["']${r.anchor}["']`).test(f))throw new h(`No element with id="${r.anchor}" in ${a}`);if(n.steps.some(s=>s.anchor===r.anchor))throw new h(`Step anchor '${r.anchor}' is already registered`);let l={course_id:t,position:i+1,title:r.title,kind:r.kind,anchor:r.anchor,status:"todo",done_at:null};return i<o&&ha(e,t,i+1),ia(e,l),la(e,t,new Date().toISOString()),H(t),l})}function Rt(e,t,r,n){let o=new Date().toISOString();return H(t),D(e,()=>{if(!ja(e,t,r,n,n==="done"?o:null))return null;let a=ga(e,t),f=a.length>0&&a.every(s=>s.status!=="todo"),l=ea(e,t);return l&&f&&l.status==="active"?ka(e,t,"completed",o):l&&!f&&l.status==="completed"?ka(e,t,"active",o):la(e,t,o),E(e,t)})}function _t(e,t,r){return w.test(t)&&ka(e,t,r,new Date().toISOString())?E(e,t):null}function Lt(e,t){if(!w.test(t))return false;let r=ma(e,t);return rmSync(y(t),{recursive:true,force:true}),r}function Dt(e){return {done:e.steps.filter(t=>t.status!=="todo").length,total:e.steps.length}}function C(...e){try{return execFileSync("git",e,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}var G={"github.com":"github","gitlab.com":"gitlab","bitbucket.org":"bitbucket"};function ot(e){if(!e)return [null,null];let t=/^git@([^:]+):(.+?)(?:\.git)?$/.exec(e);if(t){let n=(t[1]??"").toLowerCase();return [t[2]??e,G[n]??"local"]}let r=/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/.exec(e);if(r){let n=(r[1]??"").toLowerCase();return [r[2]??e,G[n]??"local"]}return [e,"local"]}function Tt(){return C("config","user.name")}function vt(){let e=process.cwd(),t=C("rev-parse","--abbrev-ref","HEAD"),r=C("rev-parse","HEAD"),n=C("remote","get-url","origin"),[o,i]=ot(n),a;return o?a=o.replace(/\/+$/,"").split("/").pop()??null:a=basename(e)||null,t==="HEAD"&&(t=null),{project:a,repository:o,branch:t,commit_hash:r,folder:e,repository_platform:i}}var j="That URL is too large to be a lesson.",b="Only public http(s) URLs can be imported.",ut=new Set([301,302,303,307,308]);function lt(e){return /^https?:\/\/\S+$/i.test(e.trim())}function z(e){let[t=0,r=0]=e.split(".").map(Number);return t===0||t===10||t===127||t===169&&r===254||t===172&&r>=16&&r<=31||t===192&&r===168||t===100&&r>=64&&r<=127||t>=224}function K(e){let t=e.replace(/^\[|\]$/g,"").replace(/%.*$/,"").toLowerCase();if(isIP(t)===4)return z(t);if(isIP(t)!==6)return true;let r=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(t);if(r?.[1])return z(r[1]);if(t==="::"||t==="::1")return true;let n=t.split(":")[0]??"";return !!(/^f[cd]/.test(n)||/^fe[89ab]/.test(n))}async function ct(e){return (await st.promises.lookup(e,{all:true})).map(r=>r.address)}async function pt(e,t){let r=e.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(r==="localhost"||r.endsWith(".localhost"))throw new ra(b);if(isIP(r)){if(K(r))throw new ra(b);return r}let n;try{n=await t(r);}catch{throw new ra("The URL could not be reached \u2014 copy the lesson text instead.")}let o=n[0];if(!o||n.some(i=>K(i)))throw new ra(b);return o}var ft=(e,t,r)=>new Promise((n,o)=>{let i=isIP(t)===6?6:4,a$1=(s,c,m)=>{c.all?m(null,[{address:t,family:i}]):m(null,t,i);},l=(e.protocol==="https:"?at:it).request(e,{method:"GET",lookup:a$1,signal:r,headers:{accept:"text/plain, text/markdown, application/json;q=0.9, */*;q=0.5","user-agent":`devcoach/${a}`}},s=>{let c=s.headers["content-length"];n({status:s.statusCode??0,location:s.headers.location??null,contentLength:c===void 0?null:Number(c),body:s,abort:()=>s.destroy()});});l.on("error",o),l.end();});async function ht(e,t){if(e.contentLength!==null&&e.contentLength>t)throw e.abort(),new ra(j);let r=[],n=0;for await(let o of e.body){if(n+=o.byteLength,n>t)throw e.abort(),new ra(j);r.push(o);}return Buffer.concat(r).toString("utf8")}async function Ft(e,t={}){let r=t.timeoutMs??5e3,n=t.maxBytes??256*1024,o=t.maxRedirects??3,i=t.lookup??ct,a=t.transport??ft;if(!lt(e))throw new ra("Only http(s) URLs can be imported.");let f=new AbortController,l=setTimeout(()=>f.abort(),r);try{let s=new URL(e.trim());for(let c=0;;c++){let m=await pt(s,i),p=await a(s,m,f.signal);if(ut.has(p.status)){if(p.abort(),!p.location||c>=o)throw new ra("The URL redirects too many times \u2014 copy the lesson text instead.");if(s=new URL(p.location,s),!/^https?:$/.test(s.protocol))throw new ra(b);continue}if(p.status<200||p.status>=300)throw p.abort(),new ra(`The URL answered ${p.status} \u2014 nothing to import there.`);return await ht(p,n)}}catch(s){if(s instanceof ra)throw s;let c=s?.name==="AbortError"?"timed out":"could not be reached";throw new ra(`The URL ${c} \u2014 copy the lesson text instead.`)}finally{clearTimeout(l);}}export{w as a,h as b,y as c,W as d,B as e,Ct as f,E as g,bt as h,xt as i,Rt as j,_t as k,Lt as l,Dt as m,Tt as n,vt as o,lt as p,Ft as q};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {Ba,Ca}from'./chunk-SDIK7HUX.js';import {styleText}from'util';function T(e=process.env,t=!!process.stdout.isTTY){if(e.NO_COLOR)return false;let r=e.FORCE_COLOR;return r!==void 0?!["0","false"].includes(r):t&&e.TERM!=="dumb"}var _=T(),i=(e,t)=>_?styleText(e,t,{validateStream:false}):t,c={green:e=>i("green",e),red:e=>i("red",e),yellow:e=>i("yellow",e),cyan:e=>i("cyan",e),magenta:e=>i("magenta",e),dim:e=>i("dim",e),bold:e=>i("bold",e)};function z(e,t){return e==="green"?c.green(t):e==="yellow"?c.yellow(t):c.red(t)}var K=e=>Ba(e),y=e=>Ca(e);function I(e=process.env,t=!!process.stdout.isTTY){if(e.FORCE_HYPERLINK&&e.FORCE_HYPERLINK!=="0")return true;if(!t||e.NO_COLOR||e.TERM==="dumb")return false;let r=e.TERM_PROGRAM??"";return !!(["iTerm.app","WezTerm","vscode","Hyper","ghostty","Tabby","rio"].includes(r)||["xterm-kitty","alacritty","xterm-ghostty","wezterm"].includes(e.TERM??"")||e.KITTY_WINDOW_ID||e.WT_SESSION||Number(e.VTE_VERSION??0)>=5e3||Number(e.KONSOLE_VERSION??0)>=22e4)}var N=2048,O=/^https?:\/\/\S+$/,M=e=>e.length<=N&&O.test(e)&&Ba(e)===e;function Y(e,t=e){let r=y(t);return M(e)&&I()?`\x1B]8;;${e}\x07${r}\x1B]8;;\x07`:r}var C=/\u001b\[[0-9;]*m|\u001b\]8;;[^\u0007\u001b]*(?:\u0007|\u001b\\)/g,L=e=>e.replace(C,""),S=new Intl.Segmenter("en",{granularity:"grapheme"}),W=/^[\x20-\x7e]*$/,w=/^[\p{Mn}\p{Me}\p{Cf}\p{Cc}]/u,P=new RegExp("\\p{Emoji_Presentation}|\\p{Emoji}\\u{fe0f}","u"),A=/^[\u{1100}-\u{115f}\u{2329}\u{232a}\u{2e80}-\u{303e}\u{3041}-\u{33ff}\u{3400}-\u{4dbf}\u{4e00}-\u{9fff}\u{a000}-\u{a4cf}\u{a960}-\u{a97f}\u{ac00}-\u{d7a3}\u{f900}-\u{faff}\u{fe10}-\u{fe19}\u{fe30}-\u{fe6f}\u{ff00}-\u{ff60}\u{ffe0}-\u{ffe6}\u{16fe0}-\u{16fe4}\u{17000}-\u{18aff}\u{1b000}-\u{1b2ff}\u{1f200}-\u{1f2ff}\u{20000}-\u{2fffd}\u{30000}-\u{3fffd}]/u;function H(e){return w.test(e)?0:P.test(e)||A.test(e)?2:1}function p(e){let t=L(e);if(W.test(t))return t.length;let r=0;for(let{segment:o}of S.segment(t))r+=H(o);return r}function b(e,t,r="left"){let o=Math.max(0,t-p(e));if(r==="right")return " ".repeat(o)+e;if(r==="center"){let s=Math.floor(o/2);return " ".repeat(s)+e+" ".repeat(o-s)}return e+" ".repeat(o)}function B(e,t,r,o=true){let s=t.map((n,u)=>Math.max(o?p(n.header):0,...r.map(a=>p(a[u]??"")))),d=(n,u,a)=>n+s.map(x=>"\u2500".repeat(x+2)).join(u)+a,l=n=>`\u2502${n.map((u,a)=>` ${b(u??"",s[a]??0,t[a]?.justify??"left")} `).join("\u2502")}\u2502`,E=s.reduce((n,u)=>n+u+3,1),f=[];e&&f.push(b(c.bold(e),E,"center")),f.push(d("\u256D","\u252C","\u256E")),o&&f.push(l(t.map(n=>c.bold(n.header))),d("\u251C","\u253C","\u2524"));for(let n of r)f.push(l(n));return f.push(d("\u2570","\u2534","\u256F")),f.join(`
|
|
3
|
+
`)}var $=100;function h(e,t=process.stdout,r=process.env){return [e,t.columns,Number.parseInt(r.COLUMNS??"",10)].find(s=>s!==void 0&&Number.isInteger(s)&&s>0)??80}var j=e=>e===void 0?Math.min(h(),$):h(e);function J(e="",t=j()){if(!e)return "\u2500".repeat(t);let r=`\u2500\u2500 ${e} `;return r+"\u2500".repeat(Math.max(2,t-p(r)))}var U=e=>{let t=Math.max(0,Math.min(10,Math.round(e)));return "\u2588".repeat(t)+"\u2591".repeat(10-t)},k=e=>e>=7?"green":e>=4?"yellow":"red";export{c as a,z as b,K as c,y as d,Y as e,B as f,J as g,U as h,k as i};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {a}from'./chunk-ILDBPNYJ.js';import {readFileSync,existsSync,lstatSync,mkdirSync,writeFileSync}from'fs';import {dirname,join}from'path';import {fileURLToPath}from'url';import {z as z$1}from'zod';import {createRequire}from'module';import {homedir}from'os';import {strToU8,zipSync,unzipSync,deflateSync,strFromU8,Inflate}from'fflate';var ht=dirname(fileURLToPath(import.meta.url)),Et='<script data-devcoach="highlighter"></script>',be=/<script data-devcoach="highlighter">\s*<\/script>/,St=/<script data-devcoach="highlighter">\s*(?!<\/script>)\S/;function _t(){for(let e of ["../assets","../../assets"]){let t=join(ht,e,"static","vendor");if(existsSync(join(t,"highlight.min.js")))return t}return null}var Le={light:{keyword:"#0033b3",string:"#067d17",comment:"#5f6672",number:"#1750eb",title:"#00627a",type:"#871094",attr:"#374151",meta:"#7a6a00"},dark:{keyword:"#cf8e6d",string:"#6aab73",comment:"#8b8f96",number:"#2aacb8",title:"#56a8f5",type:"#c77dbb",attr:"#bcbec4",meta:"#b3ae60"}};function Re(e){return Object.entries(e).map(([t,n])=>`--syn-${t}:${n}`).join(";")}function yt(){let e=Re(Le.light),t=Re(Le.dark);return [`:where(:root){${e}}`,`@media (prefers-color-scheme: dark){:where(:root:not([data-theme="light"])){${t}}}`,`:where(:root[data-theme="dark"]){${t}}`,"pre code.hljs{display:block;background:transparent;color:inherit;padding:0}",".hljs-comment,.hljs-quote{color:var(--syn-comment);font-style:italic}",".hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-doctag{color:var(--syn-keyword)}",".hljs-string,.hljs-regexp,.hljs-addition,.hljs-selector-attr,.hljs-selector-pseudo{color:var(--syn-string)}",".hljs-number,.hljs-symbol,.hljs-bullet,.hljs-link{color:var(--syn-number)}",".hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:var(--syn-title)}",".hljs-type,.hljs-built_in,.hljs-title.class_,.hljs-tag{color:var(--syn-type)}",".hljs-attr,.hljs-attribute,.hljs-variable,.hljs-template-variable,.hljs-property,.hljs-params{color:var(--syn-attr)}",".hljs-meta,.hljs-deletion,.hljs-subst{color:var(--syn-meta)}",".hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}"].join("")}var Tt=';(function(){function run(){hljs.configure({ignoreUnescapedHTML:true});document.querySelectorAll("pre code").forEach(function(el){if(el.dataset.highlighted||el.classList.contains("nohighlight"))return;hljs.highlightElement(el);});}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",run);else run();})();',T;function Ne(){if(T!==void 0)return T;T=null;try{let e=_t();if(e===null)return T;let t=readFileSync(join(e,"highlight.min.js"),"utf8"),n=readFileSync(join(e,"highlight.LICENSE.txt"),"utf8").trim();if(/<\/script/i.test(t)||n.includes("*/"))return T;T=`<style data-devcoach="highlighter">${yt()}</style>
|
|
3
|
+
<script data-devcoach="highlighter">/*
|
|
4
|
+
Syntax highlighting by highlight.js, inlined by devcoach.
|
|
5
|
+
|
|
6
|
+
${n}
|
|
7
|
+
*/
|
|
8
|
+
${t}
|
|
9
|
+
${Tt}</script>`;}catch{T=null;}return T}function bt(e){return St.test(e)}function Lt(e){return /<pre[\s>][\s\S]*?<code[\s>]/.test(e)}function xe(e){if(bt(e)||!be.test(e)||!Lt(e))return null;let t=Ne();return t===null?null:e.replace(be,()=>t)}function Oe(e){let t=Ne();return t===null||!e.includes(t)?e:e.replace(t,()=>Et)}var K=z$1.enum(["junior","mid","senior"]),G=z$1.enum(["github","gitlab","bitbucket","local"]),Rt=z$1.enum(["know","understood","dont_know"]),ir=z$1.number().int().min(0).max(10);function Y(e){return e.toISOString().replace(/\.\d{3}Z$/,"Z")}function Nt(e){if(e instanceof Date){if(Number.isNaN(e.getTime()))throw new TypeError("Invalid Date");return e}let t=e.trim();/[zZ]$|[+-]\d{2}:?\d{2}$/.test(t)||(t=t.includes("T")||t.includes(" ")?`${t.replace(" ","T")}Z`:`${t}T00:00:00Z`);let r=new Date(t);if(Number.isNaN(r.getTime()))throw new TypeError(`Cannot parse timestamp ${JSON.stringify(e)} \u2014 expected ISO 8601`);return r}function xt(e){let t=Nt(e),n=new Date;return t.getTime()>n.getTime()&&(t=n),Y(t)}var Ot=z$1.union([z$1.string(),z$1.date()]).transform((e,t)=>{try{return xt(e)}catch(n){return t.addIssue({code:"custom",message:n.message}),z$1.NEVER}}),_=z$1.string().nullish().transform(e=>e??null),wt=z$1.object({id:z$1.string(),timestamp:Ot,topic_id:z$1.string(),categories:z$1.array(z$1.string()),title:z$1.string(),level:K,summary:z$1.string(),body:_,task_context:_,project:_,repository:_,branch:_,commit_hash:_,folder:_,repository_platform:G.nullish().transform(e=>e??null),starred:z$1.boolean().default(false),feedback:Rt.nullish().transform(e=>e??null),imported:z$1.boolean().default(false),shared_by:_}),Dt=z$1.object({topic:z$1.string(),confidence:z$1.number().int()}),Ct=z$1.object({name:z$1.string(),topics:z$1.array(z$1.string())});z$1.object({knowledge:z$1.array(Dt),groups:z$1.array(Ct)});var It=z$1.enum(["system","dark","light"]),At=z$1.enum(["auto","lessons","knowledge"]),kt=z$1.enum(["session","global"]);z$1.object({max_per_day:z$1.number().int().default(2),min_gap_minutes:z$1.number().int().default(240),ui_theme:It.default("system"),ui_home:At.default("auto"),nudge_every:z$1.number().int().min(0).default(10),nudge_scope:kt.default("session"),share_name:z$1.string().nullable().default(null)});var vt=z$1.enum(["active","completed","abandoned"]),Ut=z$1.enum(["concept","example","practice","check"]),Mt=z$1.enum(["todo","done","skipped"]),Ft=z$1.object({concept:z$1.string().min(1),known:z$1.boolean()}),z=z$1.object({course_id:z$1.string(),position:z$1.number().int().min(1),title:z$1.string().min(1),kind:Ut,anchor:z$1.string().regex(/^[a-z0-9][a-z0-9-]*$/),status:Mt.default("todo"),done_at:_}),q=z$1.object({id:z$1.string().regex(/^[a-z0-9-]+$/),lesson_id:_,topic_id:z$1.string().min(1),title:z$1.string().min(1),goal:_,prerequisites:z$1.array(Ft).default([]),status:vt.default("active"),created_at:z$1.string(),updated_at:z$1.string(),completed_at:_});function x(e){return wt.parse(e)}var {DatabaseSync:ve}=createRequire(import.meta.url)("node:sqlite"),ie=process.env.DEVCOACH_DIR??join(homedir(),".devcoach"),$=join(ie,"coaching.db"),U=join(ie,"learning-state.md"),Ue=join(ie,"courses"),V="settings.json",J="lessons.json",Z="knowledge.json",Q="learning-state.md",ee="courses.json",M="index.html",Xt=new RegExp(`^courses/([a-z0-9-]+)/${M.replace(".","\\.")}$`),Sr={engineering:8,architecture:8,patterns:7,debugging:8,node:7,javascript:7,typescript:6,python:4,django:3,fastapi:4,docker:8,traefik:7,coolify:7,postgresql:6,redis:6,git:7,ci_cd:6,security:5,performance:6,testing:5,linux:7,networking:6,react:5,frontend:5},Wt=80;function Kt(e){return e.trim().slice(0,Wt)}var Gt={max_per_day:"2",min_gap_minutes:"240",ui_theme:"system",ui_home:"auto",nudge_every:"10",nudge_scope:"session",share_name:""},Yt=50;function h(e,t,...n){return e.prepare(t).all(...n)}function E(e,t,...n){return e.prepare(t).get(...n)}function l(e,t,...n){return Number(e.prepare(t).run(...n).changes)}function De(e=$,t=false){e!==":memory:"&&mkdirSync(dirname(e),{recursive:true});let n=new ve(e,{readOnly:t});return n.exec("PRAGMA busy_timeout = 3000"),n}var v=6,zt="2.6.0",te="schema_app_version",ne="schema_min_app_version";function qt(e){let t=e.upgradedBy?`devcoach ${e.upgradedBy}`:"a newer devcoach",n=e.minApp?`devcoach \u2265 ${e.minApp}`:"a newer devcoach";return `The coaching database was upgraded by ${t} (schema v${e.dbSchema}); this is devcoach ${a} (schema v${e.appSchema}). Reads work; writes need ${n} \u2014 update this install, or use the newer channel.`}var O=class extends Error{constructor(n){super(qt(n));this.compat=n;this.name="SchemaTooNewError";}compat},Me=new WeakMap;function Fe(e){return Me.get(e)??null}function $e(e){try{let t=h(e,"SELECT key, value FROM settings WHERE key IN (?, ?)",te,ne),n=r=>t.find(s=>s.key===r)?.value??null;return {upgradedBy:n(te),minApp:n(ne)}}catch{return {upgradedBy:null,minApp:null}}}function je(e){let t=e.prepare("PRAGMA user_version").get();return Number(t?.user_version??0)}function _r(e=$){if(!existsSync(e))return {exists:false,schema:null,upgradedBy:null};let t=null;try{return t=new ve(e,{readOnly:!0}),{exists:!0,schema:je(t),upgradedBy:$e(t).upgradedBy}}catch{return {exists:true,schema:null,upgradedBy:null}}finally{t?.close();}}function Vt(e=$){let t=De(e),n=je(t);return n>v?(t.close(),t=De(e,true),t.exec("PRAGMA query_only = 1"),Me.set(t,{readOnly:true,dbSchema:n,appSchema:v,...$e(t)}),t):(n<v&&(Zt(t,n),t.exec(`PRAGMA user_version = ${v}`)),t)}function Pe(e,t){let n=Fe(e);if(n)throw new O(n);e.exec("BEGIN IMMEDIATE");try{let r=t();return e.exec("COMMIT"),r}catch(r){try{e.exec("ROLLBACK");}catch{}throw r}}function yr(e,t=$){let n=Vt(t);try{return e(n)}catch(r){let s=Fe(n);throw s&&!(r instanceof O)&&/readonly database/i.test(String(r))?new O(s):r}finally{n.close();}}function Jt(e,t){try{return e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!==void 0}catch{return false}}function Zt(e,t=0){let n=Jt(e,"courses");e.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS lessons (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
timestamp TEXT NOT NULL,
|
|
13
|
+
topic_id TEXT NOT NULL,
|
|
14
|
+
categories TEXT NOT NULL,
|
|
15
|
+
title TEXT NOT NULL,
|
|
16
|
+
level TEXT NOT NULL,
|
|
17
|
+
summary TEXT NOT NULL,
|
|
18
|
+
body TEXT,
|
|
19
|
+
task_context TEXT,
|
|
20
|
+
project TEXT,
|
|
21
|
+
repository TEXT,
|
|
22
|
+
branch TEXT,
|
|
23
|
+
commit_hash TEXT,
|
|
24
|
+
folder TEXT,
|
|
25
|
+
feedback TEXT,
|
|
26
|
+
repository_platform TEXT,
|
|
27
|
+
starred INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
imported INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
shared_by TEXT
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS knowledge (
|
|
33
|
+
topic TEXT PRIMARY KEY,
|
|
34
|
+
confidence INTEGER NOT NULL DEFAULT 5,
|
|
35
|
+
updated_at TEXT NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
39
|
+
key TEXT PRIMARY KEY,
|
|
40
|
+
value TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS knowledge_group_names (
|
|
44
|
+
group_name TEXT PRIMARY KEY
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS knowledge_groups (
|
|
48
|
+
group_name TEXT NOT NULL,
|
|
49
|
+
topic TEXT NOT NULL,
|
|
50
|
+
PRIMARY KEY (group_name, topic)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
-- Courses (v5): the index of ~/.devcoach/courses/<id>/index.html \u2014 one rich document per
|
|
54
|
+
-- course, steps are its sections. prerequisites = JSON [{concept, known}] from the Q&A.
|
|
55
|
+
CREATE TABLE IF NOT EXISTS courses (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
lesson_id TEXT,
|
|
58
|
+
topic_id TEXT NOT NULL,
|
|
59
|
+
title TEXT NOT NULL,
|
|
60
|
+
goal TEXT,
|
|
61
|
+
prerequisites TEXT NOT NULL DEFAULT '[]',
|
|
62
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
63
|
+
created_at TEXT NOT NULL,
|
|
64
|
+
updated_at TEXT NOT NULL,
|
|
65
|
+
completed_at TEXT
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS course_steps (
|
|
69
|
+
course_id TEXT NOT NULL,
|
|
70
|
+
position INTEGER NOT NULL,
|
|
71
|
+
title TEXT NOT NULL,
|
|
72
|
+
kind TEXT NOT NULL,
|
|
73
|
+
anchor TEXT NOT NULL,
|
|
74
|
+
status TEXT NOT NULL DEFAULT 'todo',
|
|
75
|
+
done_at TEXT,
|
|
76
|
+
PRIMARY KEY (course_id, position)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
-- One-shot data migrations, by name: a stamp downgraded by an older build must never
|
|
80
|
+
-- make a newer one repeat them (v6; runtime metadata, not backed up).
|
|
81
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
82
|
+
name TEXT PRIMARY KEY,
|
|
83
|
+
applied_at TEXT NOT NULL
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_courses_lesson_id ON courses (lesson_id);
|
|
87
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_course_steps_anchor ON course_steps (course_id, anchor);
|
|
88
|
+
|
|
89
|
+
-- Runtime-only: per-session interaction counter for lesson-cue pacing.
|
|
90
|
+
-- Never exported/imported (backup carries config, not this state).
|
|
91
|
+
CREATE TABLE IF NOT EXISTS nudge_state (
|
|
92
|
+
session_id TEXT PRIMARY KEY,
|
|
93
|
+
interactions INTEGER NOT NULL DEFAULT 0,
|
|
94
|
+
updated_at TEXT NOT NULL
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
-- Runtime-only, single row: cue lifecycle. pending=1 between an emitted cue and
|
|
98
|
+
-- its resolution (log_lesson or skip_lesson), arming a shorter retry threshold.
|
|
99
|
+
CREATE TABLE IF NOT EXISTS cue_state (
|
|
100
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
101
|
+
pending INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
last_cue_at TEXT,
|
|
103
|
+
last_skip_reason TEXT
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_lessons_timestamp ON lessons (timestamp);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_lessons_starred_ts ON lessons (starred, timestamp);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_lessons_feedback ON lessons (feedback);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_lessons_topic_id ON lessons (topic_id);
|
|
110
|
+
`),tn(e,t,n),nn(e),y(e,te,a),y(e,ne,zt);}var Ce="v4-feedback-understood";function Qt(e,t){return e.prepare("SELECT 1 FROM schema_migrations WHERE name = ?").get(t)!==void 0}function en(e,t){l(e,"INSERT OR IGNORE INTO schema_migrations (name, applied_at) VALUES (?, ?)",t,new Date().toISOString());}function tn(e,t,n){for(let r of ["ALTER TABLE lessons ADD COLUMN body TEXT","ALTER TABLE lessons ADD COLUMN imported INTEGER NOT NULL DEFAULT 0","ALTER TABLE lessons ADD COLUMN shared_by TEXT"])try{e.exec(r);}catch{}Qt(e,Ce)||(t<4&&!n&&e.exec("UPDATE lessons SET feedback = 'understood' WHERE feedback = 'dont_know'"),en(e,Ce));}function nn(e){for(let[t,n]of Object.entries(Gt))l(e,"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",t,n);}var He="(id, timestamp, topic_id, categories, title, level, summary, body, task_context, project, repository, branch, commit_hash, folder, repository_platform, starred, feedback, imported, shared_by)",Be="(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function Xe(e){return [e.id,e.timestamp,e.topic_id,JSON.stringify(e.categories),e.title,e.level,e.summary,e.body,e.task_context,e.project,e.repository,e.branch,e.commit_hash,e.folder,e.repository_platform,e.starred?1:0,e.feedback,e.imported?1:0,e.shared_by]}function Tr(e,t){l(e,`INSERT OR REPLACE INTO lessons ${He} VALUES ${Be}`,...Xe(t));}function ae(e,t){return l(e,`INSERT OR IGNORE INTO lessons ${He} VALUES ${Be}`,...Xe(t))>0}var rn=[["project","project"],["repository","repository"],["branch","branch"],["commit","commit_hash"]];function sn(e,t){if(e.date_from==null&&e.date_to==null){let n=Tn(e.period??null);n!=null&&t("timestamp >= ?",n);return}if(e.date_from!=null&&t("timestamp >= ?",e.date_from),e.date_to!=null){let n=e.date_to.includes("T")||e.date_to.includes(" ");t("timestamp <= ?",n?e.date_to:`${e.date_to}T23:59:59`);}}function We(e){let t=[],n=[],r=(o,...i)=>{t.push(o),n.push(...i);};sn(e,r),e.category!=null&&r("categories LIKE ?",`%"${e.category}"%`),e.level!=null&&r("level = ?",e.level);for(let[o,i]of rn){let a=e[o];a!=null&&r(`${i} LIKE ?`,`%${a}%`);}if(e.starred!=null&&r("starred = ?",e.starred?1:0),e.imported!=null&&r("imported = ?",e.imported?1:0),e.shared_by!=null&&r("imported = 1 AND shared_by = ?",e.shared_by),e.search!=null)for(let o of e.search.split(/\s+/).filter(Boolean)){let i=`%${o}%`;r("(title LIKE ? OR topic_id LIKE ? OR summary LIKE ? OR body LIKE ? OR shared_by LIKE ?)",i,i,i,i,i);}return e.feedback==="none"?r("feedback IS NULL"):e.feedback!=null&&r("feedback = ?",e.feedback),{where:t.length?`WHERE ${t.join(" AND ")}`:"",params:n}}function ce(e,t={}){let{where:n,params:r}=We(t),s=E(e,`SELECT COUNT(*) AS n FROM lessons ${n}`,...r);return Number(s?.n??0)}var on=new Set(["timestamp","level","topic_id","title","feedback"]),an=new Set(["project","repository","branch","commit_hash","repository_platform"]);function br(e,t={}){let{where:n,params:r}=We(t),s=t.sort&&on.has(t.sort)?t.sort:"timestamp",o=(t.order??"desc").toLowerCase()==="asc"?"ASC":"DESC",i=`SELECT * FROM lessons ${n} ORDER BY ${s} ${o}`,a=[...r];if(t.page!=null){let u=t.per_page??25;i+=" LIMIT ? OFFSET ?",a.push(u,(t.page-1)*u);}return h(e,i,...a).map(he)}function Lr(e,t){return l(e,"DELETE FROM lessons WHERE id = ?",t)>0}function Rr(e,t){if(t.length===0)return 0;let n=t.map(()=>"?").join(", ");return l(e,`DELETE FROM lessons WHERE id IN (${n})`,...t)}function Nr(e,t,n){return l(e,"UPDATE lessons SET starred = ? WHERE id = ?",n?1:0,t)>0}function Ke(e,t,n){l(e,"UPDATE lessons SET feedback = ? WHERE id = ?",n||null,t);let r=E(e,"SELECT topic_id FROM lessons WHERE id = ?",t);return r?r.topic_id:null}function cn(e){return h(e,"SELECT * FROM lessons ORDER BY timestamp DESC").map(he)}function ue(e,t){let n=0,r=0,s=0;for(let o of t){let i;try{i=x(o);}catch{s+=1;continue}ae(e,i)?n+=1:r+=1;}return {inserted:n,duplicated:r,invalid:s}}function xr(e,t){if(!an.has(t))throw new Error(`Column not allowed: ${JSON.stringify(t)}`);return h(e,`SELECT DISTINCT ${t} FROM lessons WHERE ${t} IS NOT NULL ORDER BY ${t}`).map(r=>r[t])}function Or(e){return h(e,"SELECT DISTINCT shared_by FROM lessons WHERE imported = 1 AND shared_by IS NOT NULL ORDER BY shared_by").map(t=>t.shared_by)}function Ge(e,t){let n=E(e,"SELECT * FROM lessons WHERE id = ?",t);return n?he(n):null}function wr(e){return h(e,"SELECT DISTINCT value FROM lessons, json_each(lessons.categories) ORDER BY value").map(n=>n.value)}function Ye(e){return h(e,"SELECT DISTINCT topic_id FROM lessons").map(t=>t.topic_id)}function j(e,t){let n=E(e,"SELECT COUNT(*) AS n FROM lessons WHERE timestamp >= ? AND imported = 0",t);return Number(n?.n??0)}function ze(e){let t=E(e,"SELECT timestamp FROM lessons WHERE imported = 0 ORDER BY timestamp DESC LIMIT 1");return t?t.timestamp:null}function w(e){let t={};for(let n of h(e,"SELECT topic, confidence FROM knowledge"))t[n.topic]=Number(n.confidence);return t}function le(e){return h(e,"SELECT topic, confidence FROM knowledge ORDER BY topic").map(t=>({topic:t.topic,confidence:Number(t.confidence)}))}function pe(e){let t=h(e,"SELECT group_name FROM knowledge_group_names ORDER BY group_name").map(r=>r.group_name),n=new Map;for(let r of h(e,"SELECT group_name, topic FROM knowledge_groups ORDER BY group_name, topic")){let s=r.group_name,o=n.get(s)??[];o.push(r.topic),n.set(s,o);}return t.map(r=>({name:r,topics:n.get(r)??[]}))}function F(e,t,n){let r=Math.max(0,Math.min(10,n)),s=new Date().toISOString();l(e,`INSERT INTO knowledge (topic, confidence, updated_at) VALUES (?, ?, ?)
|
|
111
|
+
ON CONFLICT(topic) DO UPDATE SET confidence = excluded.confidence, updated_at = excluded.updated_at`,t,r,s);}function Dr(e,t){let n=l(e,"DELETE FROM knowledge WHERE topic = ?",t);return l(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),n>0}function Ie(e,t){let n=t.trim();if(!n)throw new Error("Group name must not be empty");return l(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n)>0}function Cr(e,t){return l(e,"DELETE FROM knowledge_groups WHERE group_name = ?",t),l(e,"DELETE FROM knowledge_group_names WHERE group_name = ?",t)>0}function un(e,t,n){l(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n),l(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),l(e,"INSERT OR IGNORE INTO knowledge_groups (group_name, topic) VALUES (?, ?)",n,t);}function Ir(e,t){l(e,"DELETE FROM knowledge_groups WHERE topic = ?",t);}function D(e){let t={};for(let A of h(e,"SELECT key, value FROM settings"))t[A.key]=A.value;let n=t.min_gap_minutes,r=t.min_hours_between,s;n!==void 0?s=Number.parseInt(n,10):r!==void 0?s=Number.parseInt(r,10)*60:s=240;let o=t.max_per_day,i=t.ui_theme??"system",a=i==="dark"||i==="light"||i==="system"?i:"system",u=t.ui_home??"auto",p=u==="lessons"||u==="knowledge"||u==="auto"?u:"auto",d=t.nudge_every,m=d!==void 0?Number.parseInt(d,10):10,S=t.nudge_scope==="global"?"global":"session";return {max_per_day:o!==void 0?Number.parseInt(o,10):2,min_gap_minutes:s,ui_theme:a,ui_home:p,nudge_every:Number.isFinite(m)&&m>=0?m:10,nudge_scope:S,share_name:t.share_name?.trim()||null}}function y(e,t,n){l(e,"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",t,n);}function qe(e,t,n){let r=E(e,"INSERT INTO nudge_state (session_id, interactions, updated_at) VALUES (?, 1, ?) ON CONFLICT(session_id) DO UPDATE SET interactions = interactions + 1, updated_at = excluded.updated_at RETURNING interactions",t,new Date().toISOString()),s=Number(r?.interactions??0);return s===1&&l(e,"DELETE FROM nudge_state WHERE session_id NOT IN (SELECT session_id FROM nudge_state ORDER BY updated_at DESC LIMIT ?)",Yt),n==="global"?de(e,t,n):s}function Ar(e){return h(e,"SELECT session_id, interactions, updated_at FROM nudge_state ORDER BY updated_at DESC").map(t=>({session_id:String(t.session_id),interactions:Number(t.interactions),updated_at:String(t.updated_at)}))}function de(e,t,n){let r=n==="global"?E(e,"SELECT COALESCE(SUM(interactions), 0) AS n FROM nudge_state"):E(e,"SELECT interactions AS n FROM nudge_state WHERE session_id = ?",t);return Number(r?.n??0)}function kr(e){l(e,"DELETE FROM nudge_state"),l(e,"UPDATE cue_state SET pending = 0 WHERE id = 1");}var me=3;function ge(e){let t=E(e,"SELECT pending, last_cue_at, last_skip_reason FROM cue_state WHERE id = 1");return {pending:Number(t?.pending??0)===1,last_cue_at:t?.last_cue_at??null,last_skip_reason:t?.last_skip_reason??null}}function Ve(e){l(e,"INSERT INTO cue_state (id, pending, last_cue_at) VALUES (1, 1, ?) ON CONFLICT(id) DO UPDATE SET pending = 1, last_cue_at = excluded.last_cue_at",new Date().toISOString()),l(e,"DELETE FROM nudge_state");}function vr(e,t){l(e,"INSERT INTO cue_state (id, pending, last_skip_reason) VALUES (1, 0, ?) ON CONFLICT(id) DO UPDATE SET pending = 0, last_skip_reason = excluded.last_skip_reason",t??null),l(e,"DELETE FROM nudge_state");}function fe(e){let t=E(e,"SELECT COUNT(*) AS n FROM knowledge");return {knowledge_ready:Number(t?.n??0)>0}}function Ur(e){let t={};for(let n of ["project","repository","branch","repository_platform"]){let r=E(e,`SELECT ${n} AS v, COUNT(*) AS c FROM lessons WHERE ${n} IS NOT NULL AND imported = 0 GROUP BY ${n} ORDER BY c DESC LIMIT 1`);t[n]=r?r.v:null;}return t}function Je(e){return q.parse({...e,prerequisites:JSON.parse(String(e.prerequisites??"[]"))})}function ln(e){return z.parse(e)}function pn(e,t){l(e,"INSERT INTO courses (id, lesson_id, topic_id, title, goal, prerequisites, status, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",t.id,t.lesson_id,t.topic_id,t.title,t.goal,JSON.stringify(t.prerequisites),t.status,t.created_at,t.updated_at,t.completed_at);}function dn(e,t){let n=E(e,"SELECT * FROM courses WHERE id = ?",t);return n?Je(n):null}function mn(e,t={}){let n=[],r=[];t.status!=null&&(n.push("status = ?"),r.push(t.status)),t.lesson_id!=null&&(n.push("lesson_id = ?"),r.push(t.lesson_id));let s=`SELECT * FROM courses ${n.length?`WHERE ${n.join(" AND ")}`:""} ORDER BY updated_at DESC`;return h(e,s,...r).map(Je)}function gn(e,t){return h(e,"SELECT * FROM course_steps WHERE course_id = ? ORDER BY position",t).map(ln)}function Mr(e,t,n){l(e,"UPDATE course_steps SET position = position + ? WHERE course_id = ? AND position >= ?",1e5,t,n),l(e,"UPDATE course_steps SET position = position - ? WHERE course_id = ? AND position >= ?",99999,t,1e5);}function fn(e,t){l(e,"INSERT INTO course_steps (course_id, position, title, kind, anchor, status, done_at) VALUES (?, ?, ?, ?, ?, ?, ?)",t.course_id,t.position,t.title,t.kind,t.anchor,t.status,t.done_at);}function Fr(e,t,n,r,s){return l(e,"UPDATE course_steps SET status = ?, done_at = ? WHERE course_id = ? AND position = ?",r,s,t,n)>0}function $r(e,t,n,r){return l(e,"UPDATE courses SET status = ?, updated_at = ?, completed_at = CASE WHEN ? = 'completed' THEN ? ELSE NULL END WHERE id = ?",n,r,n,r,t)>0}function jr(e,t,n){l(e,"UPDATE courses SET updated_at = ? WHERE id = ?",n,t);}function Pr(e,t){return l(e,"DELETE FROM course_steps WHERE course_id = ?",t),l(e,"DELETE FROM courses WHERE id = ?",t)>0}function Hr(e){return E(e,"SELECT 1 AS one FROM courses WHERE status = 'active' LIMIT 1")!==void 0}function Br(e){let t=D(e),n=cn(e),r=le(e),s=pe(e),o=new Map;for(let p of s)for(let d of p.topics)o.set(d,p.name);let i={groups:s.map(p=>p.name),topics:r.map(p=>({topic:p.topic,confidence:p.confidence,group:o.get(p.topic)??null}))},a={[V]:strToU8(JSON.stringify(t,null,2)),[J]:strToU8(JSON.stringify(n,null,2)),[Z]:strToU8(JSON.stringify(i,null,2))};existsSync(U)&&(a[Q]=strToU8(readFileSync(U,"utf8")));let u=mn(e);if(u.length){let p=u.flatMap(d=>gn(e,d.id));a[ee]=strToU8(JSON.stringify({courses:u,steps:p},null,2));for(let d of u){let m=join(Ue,d.id,M);try{let S=lstatSync(m);S.isFile()&&S.size<=re&&(a[`courses/${d.id}/${M}`]=strToU8(Oe(readFileSync(m,"utf8"))));}catch{}}}return zipSync(a)}var re=2*1024*1024;function hn(e,t,n){if(!(V in t))return;let r=JSON.parse(strFromU8(t[V]));"max_per_day"in r&&y(e,"max_per_day",String(r.max_per_day)),"min_gap_minutes"in r&&y(e,"min_gap_minutes",String(r.min_gap_minutes)),(r.ui_theme==="system"||r.ui_theme==="dark"||r.ui_theme==="light")&&y(e,"ui_theme",r.ui_theme),(r.ui_home==="auto"||r.ui_home==="lessons"||r.ui_home==="knowledge")&&y(e,"ui_home",r.ui_home),"nudge_every"in r&&y(e,"nudge_every",String(r.nudge_every)),(r.nudge_scope==="session"||r.nudge_scope==="global")&&y(e,"nudge_scope",r.nudge_scope),typeof r.share_name=="string"&&y(e,"share_name",Kt(r.share_name)),n.settings=1;}function En(e,t,n){if(!(Z in t))return;let r=JSON.parse(strFromU8(t[Z]));if(!(r&&typeof r=="object"&&Array.isArray(r.topics))){for(let[o,i]of Object.entries(r))F(e,o,i);n.topics=Object.keys(r).length;return}let s=0;for(let o of r.groups??[])Ie(e,o)&&(s+=1);for(let o of r.topics){F(e,o.topic,o.confidence);let i=o.group;i&&i!=="Other"&&(Ie(e,i)&&(s+=1),un(e,o.topic,i));}n.topics=r.topics.length,n.groups=s;}function Sn(e,t,n){if(!(J in t))return;let r=JSON.parse(strFromU8(t[J])),{inserted:s,duplicated:o,invalid:i}=ue(e,r);n.lessons=s,n.skipped=o,n.invalid=i;}function _n(e,t){Q in e&&(mkdirSync(dirname(U),{recursive:true}),writeFileSync(U,strFromU8(e[Q]),"utf8"),t.learning_state=1);}function yn(e,t,n){if(!(ee in t))return;let r=JSON.parse(strFromU8(t[ee])),s=new Set;for(let o of r.courses??[])try{let i=q.parse(o);dn(e,i.id)||(pn(e,i),n.courses+=1),s.add(i.id);}catch{n.invalid+=1;}for(let o of r.steps??[])try{let i=z.parse(o);if(!s.has(i.course_id))continue;E(e,"SELECT 1 AS one FROM course_steps WHERE course_id = ? AND position = ?",i.course_id,i.position)||fn(e,i);}catch{n.invalid+=1;}for(let[o,i]of Object.entries(t)){let a=Xt.exec(o)?.[1];if(!a||!s.has(a)||i.length>re)continue;let u=join(Ue,a),p=join(u,M);if(existsSync(p))continue;let d=xe(strFromU8(i)),m=d!==null&&Buffer.byteLength(d)<=re;mkdirSync(u,{recursive:true}),writeFileSync(p,m?d:i);}}function Xr(e,t){let n={settings:0,topics:0,groups:0,lessons:0,skipped:0,invalid:0,learning_state:0,courses:0},r=unzipSync(t);return hn(e,r,n),En(e,r,n),Sn(e,r,n),_n(r,n),yn(e,r,n),n}function Tn(e){let t=new Date,n;if(e==="today")n=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate(),0,0,0,0));else if(e==="week")n=new Date(t.getTime()-7*864e5);else if(e==="month")n=new Date(t.getTime()-30*864e5);else if(e==="year")n=new Date(t.getTime()-365*864e5);else return null;return n.toISOString()}function he(e){let t=[];try{let n=JSON.parse(e.categories);Array.isArray(n)&&(t=n);}catch{t=[];}return x({...e,categories:t,starred:!!e.starred,imported:!!e.imported})}function Ze(e){let t=[...e].length,n=Math.max(34-t-2,2),r=Math.floor(n/2);return `### ${"\u2500".repeat(r)} ${e} ${"\u2500".repeat(n-r)}`}function Qe(e){let t=e.level.charAt(0).toUpperCase()+e.level.slice(1),n=e.categories.join(" \xB7 ");return [Ze("\u{1F393} devcoach"),`**${e.title}** \xB7 ${n} \xB7 ${t}`,"",e.body?.trim()||e.summary,"",Ze(`${e.topic_id} \xB7 ${e.level}`)].join(`
|
|
112
|
+
`)}var B="devcoach.lesson",I=1,xn=`devcoach:lesson:${I}:`,On="https://ultimaphoenix.github.io/dev-coach/lesson",wn=".devcoach.md",Dn=256*1024,f=class extends Error{},C=z$1.string().nullish(),Cn=["task_context","project","branch","commit_hash"],In=new Set(["github","gitlab","bitbucket"]),X=z$1.object({format:z$1.literal(B),version:z$1.literal(I),lesson:z$1.object({title:z$1.string().min(1),summary:z$1.string(),body:z$1.string().nullable(),topic_id:z$1.string().min(1),categories:z$1.array(z$1.string()),level:K,task_context:C,project:C,repository:C,branch:C,commit_hash:C,repository_platform:G.nullish()}),origin:z$1.object({id:z$1.string().min(1),timestamp:z$1.string()}),shared_by:z$1.string().nullable(),shared_at:z$1.string(),app_version:z$1.string()});function Jr(e,t){let n={title:e.title,summary:e.summary,body:e.body,topic_id:e.topic_id,categories:e.categories,level:e.level};if(t.includeContext){for(let r of Cn)e[r]!=null&&(n[r]=e[r]);e.repository_platform&&In.has(e.repository_platform)&&(e.repository!=null&&(n.repository=e.repository),n.repository_platform=e.repository_platform);}return {format:B,version:I,lesson:n,origin:{id:e.id,timestamp:e.timestamp},shared_by:t.sharedBy,shared_at:Y(t.now??new Date),app_version:a}}function Zr(e){return e.anonymous?null:e.explicit!=null?e.explicit.trim()||null:e.setting?.trim()||e.gitUserName?.trim()||null}function tt(e){let t=deflateSync(strToU8(JSON.stringify(e)),{level:9});return xn+Buffer.from(t).toString("base64url")}var nt=/devcoach:lesson:(\d+):([A-Za-z0-9_-]+)/,An=64e3,Ee=512,H=class extends Error{};function kn(e,t){let n=[],r=0,s=new Inflate(a=>{if(r+=a.length,r>t)throw new H("too large");n.push(a);});for(let a=0;a<e.length;a+=Ee)s.push(e.subarray(a,a+Ee),a+Ee>=e.length);let o=new Uint8Array(r),i=0;for(let a of n)o.set(a,i),i+=a.length;return o}function vn(e){let t=new RegExp(`^${nt.source}$`).exec(e.trim());if(!t)throw new f("That is not a devcoach lesson code.");if(Number(t[1])!==I)throw new f(`This lesson was shared by a newer devcoach (format ${t[1]}) \u2014 upgrade to import it.`);let n=t[2]??"";if(n.length>An)throw new f("This lesson code is too large to be a lesson.");let r;try{let s=kn(new Uint8Array(Buffer.from(n,"base64url")),Dn);r=strFromU8(s);}catch(s){throw s instanceof H?new f("This lesson code is too large to be a lesson."):new f("This lesson code is damaged or incomplete \u2014 copy it again.")}return rt(r)}function rt(e){let t;try{t=JSON.parse(e);}catch{throw new f("This lesson code is damaged or incomplete \u2014 copy it again.")}let n=X.safeParse(t);if(!n.success)throw new f("This is not a valid devcoach lesson.");return n.data}function Qr(e,t=tt(e)){let n=e.shared_by?`Shared by ${e.shared_by} with devcoach`:"Shared with devcoach";return [Qe(e.lesson),"",`${n} \u2014 paste it to your agent or run: devcoach import`,t].join(`
|
|
113
|
+
`)}function es(e,t=tt(e)){return `${On}#${t}`}function Un(e,t="lesson"){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||t}function ts(e){return `${Un(e.origin.id)}${wn}`}var Mn=/^[A-Za-z0-9_.+:@/-]+$/,P=e=>Mn.test(e)?e:JSON.stringify(e),Fn=e=>`[${e.map(P).join(", ")}]`;function ns(e){let t=e.lesson,n=["---",`format: ${B}`,`version: ${I}`,`title: ${JSON.stringify(t.title)}`,`topic_id: ${P(t.topic_id)}`,`level: ${t.level}`,`categories: ${Fn(t.categories)}`,`summary: ${JSON.stringify(t.summary)}`,`origin_id: ${P(e.origin.id)}`,`origin_timestamp: ${e.origin.timestamp}`,`shared_by: ${e.shared_by==null?"null":JSON.stringify(e.shared_by)}`,`shared_at: ${e.shared_at}`,`app_version: ${P(e.app_version)}`];for(let r of ["task_context","project","repository","branch","commit_hash","repository_platform"]){let s=t[r];s!=null&&n.push(`${r}: ${JSON.stringify(s)}`);}return n.push("---","",t.body??t.summary,""),n.join(`
|
|
114
|
+
`)}function $n(e){let t=[],n="",r=false;for(let s=0;s<e.length;s++){let o=e[s]??"";r?(n+=o,o==="\\"?n+=e[++s]??"":o==='"'&&(r=false)):o==='"'?(r=true,n+=o):o===","?(t.push(n),n=""):n+=o;}return t.push(n),t}function st(e){let t=e.trim();if(t==="null"||t==="")return null;if(t.startsWith('"'))return JSON.parse(t);if(t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1).trim();return n?$n(n).map(r=>st(r)):[]}return t}function jn(e){let t=e.replace(/\r\n?/g,`
|
|
115
|
+
`).split(`
|
|
116
|
+
`);if(t[0]?.trim()!=="---")throw new f("Missing frontmatter (--- \u2026 ---).");let n=t.findIndex((a,u)=>u>0&&a.trim()==="---");if(n===-1)throw new f("Frontmatter never closes (missing ---).");let r={};for(let a of t.slice(1,n)){if(!a.trim()||a.trimStart().startsWith("#"))continue;let u=a.indexOf(":");if(u===-1)throw new f(`Cannot read frontmatter line: ${a.trim()}`);try{r[a.slice(0,u).trim()]=st(a.slice(u+1));}catch{throw new f(`Cannot read frontmatter line: ${a.trim()}`)}}let s=t.slice(n+1).join(`
|
|
117
|
+
`).trim(),o={format:r.format,version:typeof r.version=="string"?Number(r.version):r.version,lesson:{title:r.title,summary:r.summary,body:s||null,topic_id:r.topic_id,categories:r.categories,level:r.level,task_context:r.task_context??void 0,project:r.project??void 0,repository:r.repository??void 0,branch:r.branch??void 0,commit_hash:r.commit_hash??void 0,repository_platform:r.repository_platform??void 0},origin:{id:r.origin_id,timestamp:r.origin_timestamp},shared_by:r.shared_by??null,shared_at:r.shared_at,app_version:r.app_version},i=X.safeParse(o);if(!i.success)throw new f("This file is not a valid devcoach lesson.");return i.data}function ot(e){let t=e.trim();if(!t)throw new f("Nothing to import \u2014 paste a lesson code, link or file.");if(t.startsWith("[")){let s=et(t);if(Array.isArray(s))return {kind:"lessons",records:s}}else if(t.startsWith("{")){let s=et(t);if(s&&typeof s=="object")return s.format===B?{kind:"shared",source:"json",payload:rt(t)}:{kind:"lessons",records:[s]}}else if(t.startsWith("---"))return {kind:"shared",source:"markdown",payload:jn(t)};let n=[...t.matchAll(new RegExp(nt.source,"g"))];if(n.length===0)throw new f("Not a devcoach lesson: expected a devcoach:lesson code, a share link, a .devcoach.md file or a lessons JSON export.");let r;for(let s of n.reverse()){let o=[s[0]],i=Pn(t.slice(s.index??0));i!==s[0]&&o.push(i);for(let a of o)try{return {kind:"shared",source:"code",payload:vn(a)}}catch(u){r=u;}}throw r}function Pn(e){let t=e.split(`
|
|
118
|
+
`).map(r=>r.trim()),n=t[0]??"";for(let r of t.slice(1)){if(!/^[A-Za-z0-9_-]+$/.test(r))break;n+=r;}return n}function et(e){try{return JSON.parse(e)}catch{throw new f("This looks like JSON but does not parse \u2014 copy it again.")}}function it(e,t,n=new Date){let r=e.lesson;return x({id:t,timestamp:n,topic_id:r.topic_id,categories:r.categories,title:r.title,level:r.level,summary:r.summary,body:r.body,task_context:r.task_context??null,project:r.project??null,repository:r.repository??null,branch:r.branch??null,commit_hash:r.commit_hash??null,folder:null,repository_platform:r.repository_platform??null,starred:false,feedback:null,imported:true,shared_by:e.shared_by})}var Hn=50;function at(e,t=Hn){let n=e.origin.id,r=[n];for(let s=1;r.length<t;s++)r.push(s===1?`${n}-shared`:`${n}-shared-${s}`);return r}function ct(e,t){return e.imported&&e.title===t.lesson.title&&e.topic_id===t.lesson.topic_id&&(e.shared_by??null)===(t.shared_by??null)}var Bn=new Set([144,152,157,158,159]),Xn=new Set(["]","P","X","^","_"]),Wn=new Set([1564,8203,8206,8207,8232,8233,8234,8235,8236,8237,8238,8294,8295,8296,8297,65279]),W=(e,t,n)=>e>=t&&e<=n;function Kn(e){return e===9||e===10?false:e<32||W(e,127,159)||Wn.has(e)}function Se(e,t,n,r){let s=t;for(;s<e.length&&W(e.charCodeAt(s),n,r);)s++;return s}function ut(e,t){let n=Se(e,Se(e,t,48,63),32,47);return W(e.charCodeAt(n),64,126)?n+1:n}function Gn(e,t){let n=Se(e,t,32,47);return W(e.charCodeAt(n),48,126)?n+1:n}function lt(e,t){for(let n=t;n<e.length;n++){let r=e.charCodeAt(n);if(r===7||r===156)return n+1;if(r===27)return e[n+1]==="\\"?n+2:n;if(r===10)return n}return e.length}function Yn(e,t){let n=e.charCodeAt(t);if(n===155)return ut(e,t+1);if(n!==27)return lt(e,t+1);let r=e[t+1]??"";return r==="["?ut(e,t+2):Xn.has(r)?lt(e,t+2):Gn(e,t+1)}function pt(e){let t=[],n=0,r=0;for(;r<e.length;){let s=e.charCodeAt(r),o=s===27||s===155||Bn.has(s);if(!o&&!Kn(s)){r++;continue}t.push(e.slice(n,r)),r=o?Yn(e,r):r+1,n=r;}return t.push(e.slice(n)),t.join("")}function zn(e){return pt(e).replace(/[\t\n]+/g," ")}var qn=new Set(["summary","body"]);function ye(e){return _e(e,"")}function _e(e,t){return typeof e=="string"?qn.has(t)?pt(e):zn(e):Array.isArray(e)?e.map(n=>_e(n,t)):Vn(e)?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,_e(r,n)])):e}function Vn(e){if(e===null||typeof e!="object")return false;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function dt(e){try{let t=D(e),n=new Date,r=new Date(n.getTime()-24*36e5).toISOString(),s=j(e,r);if(s>=t.max_per_day)return {allowed:!1,reason:`Daily limit reached (${s}/${t.max_per_day} lessons in the last 24h)`};let o=ze(e);if(o!=null){let i=new Date(o),a=(n.getTime()-i.getTime())/6e4;if(a<t.min_gap_minutes){let u=t.min_gap_minutes-a,p=Math.floor(t.min_gap_minutes/60),d=t.min_gap_minutes%60,m=Math.trunc(u),S=Math.floor(m/60),A=m%60;return {allowed:!1,reason:`Too soon: last lesson ${a<0?`in ${Math.round(-a)}m (future timestamp)`:`${Math.round(a)}m ago`}, minimum interval is ${p}h ${d}m (${S}h ${A}m remaining)`}}}return {allowed:!0}}catch(t){return {allowed:true,reason:`Rate limit check failed: ${t}`}}}function cs(e){try{return {knowledge:le(e),groups:pe(e)}}catch{return {knowledge:[],groups:[]}}}function Jn(e,t,n){let r=w(e)[t]??5,s=Math.max(0,Math.min(10,r+n));return F(e,t,s),s}function us(e,t,n){return Pe(e,()=>{let r=e.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(t);if(!r)return null;let s=r.feedback??null,o=w(e);if(s===n)return {topic_id:r.topic_id,previous:s,delta:0,confidence:o[r.topic_id]??null};Ke(e,t,n);let i=(n==="know"?1:0)-(s==="know"?1:0),a=i!==0?Jn(e,r.topic_id,i):o[r.topic_id]??null;return {topic_id:r.topic_id,previous:s,delta:i,confidence:a}})}function Zn(e){try{let t=new Date,n=ce(e),r=new Date(t.getTime()-24*36e5).toISOString(),s=new Date(t.getTime()-7*864e5).toISOString(),o=j(e,r),i=j(e,s),a=w(e),u=Object.entries(a).sort((m,S)=>m[1]-S[1]),p=u.slice(0,5).map(([m,S])=>({topic:m,confidence:S})),d=u.slice(-5).reverse().map(([m,S])=>({topic:m,confidence:S}));return {total_lessons:n,lessons_today:o,lessons_this_week:i,imported_lessons:ce(e,{imported:!0}),weakest_topics:p,strongest_topics:d}}catch(t){return {error:String(t)}}}function Qn(e,t){let n=X.safeParse(ye(t));if(!n.success)return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:false};let r=n.data,s=Object.hasOwn(w(e),r.lesson.topic_id);for(let o of at(r)){let i=Ge(e,o);if(i){if(ct(i,r))return {kind:"shared",inserted:0,duplicated:1,invalid:0,lesson:i,topic_tracked:s};continue}let a=it(r,o);if(ae(e,a))return {kind:"shared",inserted:1,duplicated:0,invalid:0,lesson:a,topic_tracked:s}}return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:s}}function ls(e,t){let n=ot(t);return n.kind==="lessons"?{kind:"lessons",...ue(e,n.records.map(ye)),lesson:null,topic_tracked:null}:Qn(e,n.payload)}function ps(e){try{return Ye(e)}catch{return []}}function ds(e,t,n){let r=a=>({cue:false,reason:a,nextLessonNumber:0});if(n.planMode)return r("plan mode (not counted)");if(!fe(e).knowledge_ready)return r("onboarding not complete");let s=D(e),o="pacing disabled (nudge_every=0)";if(s.nudge_every>0){let a=qe(e,t??"__nosession__",s.nudge_scope),{pending:u}=ge(e),p=u?Math.min(me,s.nudge_every):s.nudge_every;if(o=`${a}/${p}${u?" (retry window)":""}`,a<p)return r(`paced (${o})`)}let i=dt(e);return i.allowed?(Ve(e),{cue:true,reason:`cue (${o})`,nextLessonNumber:Number(Zn(e).total_lessons??0)+1}):r(`rate limited: ${i.reason??"denied"}`)}function ms(e,t){try{let n=[],r=!0;if(!fe(e).knowledge_ready)return {wouldCue:!1,reasons:["onboarding not complete \u2014 no knowledge topics yet"]};let s=D(e);if(s.nudge_every>0){let i=de(e,t??"__nosession__",s.nudge_scope)+1,{pending:a}=ge(e),u=a?Math.min(me,s.nudge_every):s.nudge_every;i<u?(r=!1,n.push(`pacing: next stop would be ${i}/${u}${a?" (retry window)":""}`)):n.push(`pacing: next stop reaches the threshold (${i}/${u})`);}else n.push("pacing disabled (nudge_every=0) \u2014 every eligible stop cues");let o=dt(e);return o.allowed?n.push("rate limit: allowed"):(r=!1,n.push(`rate limited: ${o.reason??"denied"}`)),{wouldCue:r,reasons:n}}catch(n){return {wouldCue:false,reasons:[`check failed: ${n}`]}}}export{ge as $,Fe as A,ns as Aa,$e as B,pt as Ba,_r as C,zn as Ca,Pe as D,ye as Da,yr as E,dt as Ea,Tr as F,cs as Fa,ce as G,Jn as Ga,br as H,us as Ha,Lr as I,Zn as Ia,Rr as J,ls as Ja,Nr as K,ps as Ka,cn as L,ds as La,xr as M,ms as Ma,Or as N,Ge as O,wr as P,le as Q,F as R,Dr as S,Ie as T,Cr as U,un as V,Ir as W,D as X,y as Y,Ar as Z,kr as _,Ne as a,vr as aa,bt as b,fe as ba,Lt as c,Ur as ca,xe as d,pn as da,K as e,dn as ea,G as f,mn as fa,Rt as g,gn as ga,ir as h,Mr as ha,It as i,fn as ia,At as j,Fr as ja,kt as k,$r as ka,vt as l,jr as la,Ut as m,Pr as ma,Mt as n,Hr as na,x as o,Br as oa,ie as p,re as pa,$ as q,Xr as qa,U as r,f as ra,Ue as s,Jr as sa,M as t,Zr as ta,Sr as u,tt as ua,Wt as v,vn as va,Kt as w,Qr as wa,v as x,es as xa,qt as y,Un as ya,O as z,ts as za};
|