devcoach 2.7.0-next.626.g8dfe1b6 → 2.7.0-next.629.g276d1e7
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/README.md +1 -1
- package/dist/{app-5GWLWY6K.js → app-P5HADOIH.js} +6 -6
- package/dist/bin.js +1 -1
- package/dist/chunk-3XARTIBH.js +3 -0
- package/dist/chunk-4UBIR363.js +118 -0
- package/dist/chunk-LOJI43UC.js +15 -0
- package/dist/chunk-O5DVS7P7.js +2 -0
- package/dist/chunk-SOL7WJIT.js +2 -0
- package/dist/commands-7MWPFXSR.js +37 -0
- package/dist/{hooks-WOJMGJK3.js → hooks-FFTQT2OO.js} +1 -1
- package/dist/server-SF3PTN52.js +11 -0
- package/package.json +1 -1
- package/dist/chunk-7Y6JVSP7.js +0 -15
- package/dist/chunk-I7WLGPSM.js +0 -2
- package/dist/chunk-ZOCFTFOD.js +0 -118
- package/dist/commands-WMG3HUL7.js +0 -36
- package/dist/server-YHONDXJY.js +0 -9
package/README.md
CHANGED
|
@@ -486,7 +486,7 @@ Plugin releases up to 1.0.1 shipped a manifest that current Claude Code rejects,
|
|
|
486
486
|
|
|
487
487
|
**"MCP server not connecting"**
|
|
488
488
|
|
|
489
|
-
Run
|
|
489
|
+
Run **`devcoach doctor`**: it checks the command each agent starts (Claude Desktop's from `claude_desktop_config.json`, Claude Code's from `~/.claude.json`) — that it exists on the PATH that agent uses, that the Node it names exists and is ≥ 24, and which devcoach version it runs. Then `devcoach install --force` re-registers it with an absolute command, and you restart the agent. In Claude Desktop's log (`~/Library/Logs/Claude/mcp-server-devcoach.log`), `Failed to spawn process: No such file or directory` means devcoach never ran: the command, or the Node behind it, is missing — see [When devcoach does not start](https://ultimaphoenix.github.io/dev-coach/install/claude-desktop#when-devcoach-does-not-start). Every devcoach start writes one line to that log with its version, Node and install location.
|
|
490
490
|
|
|
491
491
|
**"Coaching isn't firing / no lessons appear"**
|
|
492
492
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {a as a$
|
|
2
|
+
import {a as a$3,c as c$1}from'./chunk-QU6UQJWC.js';import {p,q,h as h$1,m,a as a$1,e,g,j as j$1,l,n}from'./chunk-O5DVS7P7.js';import {z,E as E$1,A,y,G,X as X$1,Ba,Fa,Ca,R,V as V$1,S,W as W$1,Da,T,U,L,Ga,ra,va,N,M,P as P$1,H,K as K$1,Ea,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-4UBIR363.js';import {a}from'./chunk-SOL7WJIT.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={}){let e$1=new Hono;e$1.onError((t,a)=>t instanceof
|
|
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:Ca(p),stats:Fa(p),rateLimit:Ba(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=>Da(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=>Ga(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=>Ea(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
|
-
`}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=!
|
|
868
|
-
${
|
|
869
|
-
${a$
|
|
870
|
-
${a$
|
|
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(c$1(t))}${a}
|
|
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(c$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
|
-
var
|
|
2
|
+
import {c as c$1,e as e$1,f}from'./chunk-3XARTIBH.js';import'./chunk-SOL7WJIT.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-FFTQT2OO.js');o(e);}else {let{runCli:o}=await import('./commands-7MWPFXSR.js');await o();}}catch(o){o?.name==="SchemaTooNewError"&&(console.error(o.message),process.exit(3)),console.error(c(),o),process.exit(1);}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {a as a$1}from'./chunk-SOL7WJIT.js';import {realpathSync}from'fs';import {homedir}from'os';var a=24;function u(e,n=process.platform){let t=e.replace(/\\/g,"/");return n==="win32"?t.toLowerCase():t}var f=[["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 c(e){for(let[n,t]of f){let r=t.exec(e);if(r?.[1])return {manager:n,boundTo:r[1].startsWith("v")?r[1]:`v${r[1]}`}}return null}function g(e){let n=e.toLowerCase();return n.includes("/.claude/plugins/data/")||n.includes("/.claude/plugins/cache/")?"claude-plugin":n.includes("/.devcoach/gemini-ext/")?"gemini-extension":n.includes("/claude extensions/")?"claude-desktop-extension":n.includes("/_npx/")?"npx":n.includes("/cellar/devcoach/")?"homebrew":n.includes("/node_modules/devcoach/")?"npm-global":/\/(src\/bin\.ts|dist\/bin\.js)$/.test(n)?"source":"unknown"}function p(e,n=process.platform){let t=u(e,n),r=c(t),o=t.toLowerCase();return {channel:g(t),manager:r?.manager??(o.includes("/fnm_multishells/")?"fnm":null),boundTo:r?.boundTo??null,ephemeral:o.includes("/_npx/")||o.includes("/fnm_multishells/")}}function v(e,n=process.platform){let t=u(e,n),r=t.toLowerCase(),o=c(t);return o?o.manager:r.includes("/fnm_multishells/")?"fnm":r.includes("/.volta/")||r.includes("/volta/")?"volta":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 $(e){return Number.parseInt(e.split(".")[0]??"0",10)>=a?null:`devcoach ${a$1} needs Node ${a} or newer (it uses the built-in node:sqlite); this is Node ${e}`}function h(e,n=homedir(),t=process.platform){if(!n)return e;let r=t==="win32"?"\\":"/",o=t==="win32"?"%USERPROFILE%":"~";return t==="win32"?e.toLowerCase()===n.toLowerCase()?o:e.toLowerCase().startsWith(`${n.toLowerCase()}${r}`)?o+e.slice(n.length):e:e===n?o:e.startsWith(`${n}${r}`)?o+e.slice(n.length):e}function x(e){try{return realpathSync(e)}catch{return e}}function C(e={}){let n=e.platform??process.platform,t=e.execPath??process.execPath,r=e.argv1??x(process.argv[1]??"");return {version:a$1,node:e.nodeVersion??process.versions.node,execPath:t,nodeManager:v(t,n),entry:r,where:p(r,n)}}function w(e){let n=[e.channel];return e.boundTo&&n.push(`bound to Node ${e.boundTo} (${e.manager})`),e.ephemeral&&n.push("temporary location"),n.join(", ")}function R(e,n,t=[],r=homedir(),o=process.platform){let i=d=>h(d,r,o);return [`devcoach ${n.version} ${e}`,`node ${n.node} via ${n.nodeManager} (${i(n.execPath)})`,`${w(n.where)} (${i(n.entry)})`,...t].join(" \xB7 ")}
|
|
3
|
+
export{a,p as b,$ as c,h as d,C as e,w as f,R as g};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {a}from'./chunk-SOL7WJIT.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 lt=dirname(fileURLToPath(import.meta.url)),ut='<script data-devcoach="highlighter"></script>',Ee=/<script data-devcoach="highlighter">\s*<\/script>/,pt=/<script data-devcoach="highlighter">\s*(?!<\/script>)\S/;function dt(){for(let e of ["../assets","../../assets"]){let t=join(lt,e,"static","vendor");if(existsSync(join(t,"highlight.min.js")))return t}return null}var _e={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 Se(e){return Object.entries(e).map(([t,n])=>`--syn-${t}:${n}`).join(";")}function mt(){let e=Se(_e.light),t=Se(_e.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 gt=';(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 ye(){if(T!==void 0)return T;T=null;try{let e=dt();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">${mt()}</style>
|
|
3
|
+
<script data-devcoach="highlighter">/*
|
|
4
|
+
Syntax highlighting by highlight.js, inlined by devcoach.
|
|
5
|
+
|
|
6
|
+
${n}
|
|
7
|
+
*/
|
|
8
|
+
${t}
|
|
9
|
+
${gt}</script>`;}catch{T=null;}return T}function ft(e){return pt.test(e)}function ht(e){return /<pre[\s>][\s\S]*?<code[\s>]/.test(e)}function Te(e){if(ft(e)||!Ee.test(e)||!ht(e))return null;let t=ye();return t===null?null:e.replace(Ee,()=>t)}function be(e){let t=ye();return t===null||!e.includes(t)?e:e.replace(t,()=>ut)}var B=z$1.enum(["junior","mid","senior"]),W=z$1.enum(["github","gitlab","bitbucket","local"]),Et=z$1.enum(["know","understood","dont_know"]),Wn=z$1.number().int().min(0).max(10);function K(e){return e.toISOString().replace(/\.\d{3}Z$/,"Z")}function _t(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 s=new Date(t);if(Number.isNaN(s.getTime()))throw new TypeError(`Cannot parse timestamp ${JSON.stringify(e)} \u2014 expected ISO 8601`);return s}function St(e){let t=_t(e),n=new Date;return t.getTime()>n.getTime()&&(t=n),K(t)}var yt=z$1.union([z$1.string(),z$1.date()]).transform((e,t)=>{try{return St(e)}catch(n){return t.addIssue({code:"custom",message:n.message}),z$1.NEVER}}),S=z$1.string().nullish().transform(e=>e??null),Tt=z$1.object({id:z$1.string(),timestamp:yt,topic_id:z$1.string(),categories:z$1.array(z$1.string()),title:z$1.string(),level:B,summary:z$1.string(),body:S,task_context:S,project:S,repository:S,branch:S,commit_hash:S,folder:S,repository_platform:W.nullish().transform(e=>e??null),starred:z$1.boolean().default(false),feedback:Et.nullish().transform(e=>e??null),imported:z$1.boolean().default(false),shared_by:S}),bt=z$1.object({topic:z$1.string(),confidence:z$1.number().int()}),Lt=z$1.object({name:z$1.string(),topics:z$1.array(z$1.string())});z$1.object({knowledge:z$1.array(bt),groups:z$1.array(Lt)});var Rt=z$1.enum(["system","dark","light"]),Nt=z$1.enum(["auto","lessons","knowledge"]),Ot=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:Rt.default("system"),ui_home:Nt.default("auto"),nudge_every:z$1.number().int().min(0).default(10),nudge_scope:Ot.default("session"),share_name:z$1.string().nullable().default(null)});var xt=z$1.enum(["active","completed","abandoned"]),wt=z$1.enum(["concept","example","practice","check"]),Dt=z$1.enum(["todo","done","skipped"]),Ct=z$1.object({concept:z$1.string().min(1),known:z$1.boolean()}),G=z$1.object({course_id:z$1.string(),position:z$1.number().int().min(1),title:z$1.string().min(1),kind:wt,anchor:z$1.string().regex(/^[a-z0-9][a-z0-9-]*$/),status:Dt.default("todo"),done_at:S}),Y=z$1.object({id:z$1.string().regex(/^[a-z0-9-]+$/),lesson_id:S,topic_id:z$1.string().min(1),title:z$1.string().min(1),goal:S,prerequisites:z$1.array(Ct).default([]),status:xt.default("active"),created_at:z$1.string(),updated_at:z$1.string(),completed_at:S});function O(e){return Tt.parse(e)}var {DatabaseSync:De}=createRequire(import.meta.url)("node:sqlite"),re=process.env.DEVCOACH_DIR??join(homedir(),".devcoach"),$=join(re,"coaching.db"),U=join(re,"learning-state.md"),Ce=join(re,"courses"),z="settings.json",q="lessons.json",J="knowledge.json",V="learning-state.md",Z="courses.json",M="index.html",Mt=new RegExp(`^courses/([a-z0-9-]+)/${M.replace(".","\\.")}$`),ns={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},Ft=80;function $t(e){return e.trim().slice(0,Ft)}var jt={max_per_day:"2",min_gap_minutes:"240",ui_theme:"system",ui_home:"auto",nudge_every:"10",nudge_scope:"session",share_name:""},Pt=50;function h(e,t,...n){return e.prepare(t).all(...n)}function E(e,t,...n){return e.prepare(t).get(...n)}function u(e,t,...n){return Number(e.prepare(t).run(...n).changes)}function Re(e=$,t=false){e!==":memory:"&&mkdirSync(dirname(e),{recursive:true});let n=new De(e,{readOnly:t});return n.exec("PRAGMA busy_timeout = 3000"),n}var v=6,Ht="2.6.0",Q="schema_app_version",ee="schema_min_app_version";function Xt(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 x=class extends Error{constructor(n){super(Xt(n));this.compat=n;this.name="SchemaTooNewError";}compat},Ae=new WeakMap;function Ie(e){return Ae.get(e)??null}function ke(e){try{let t=h(e,"SELECT key, value FROM settings WHERE key IN (?, ?)",Q,ee),n=s=>t.find(r=>r.key===s)?.value??null;return {upgradedBy:n(Q),minApp:n(ee)}}catch{return {upgradedBy:null,minApp:null}}}function ve(e){let t=e.prepare("PRAGMA user_version").get();return Number(t?.user_version??0)}function ss(e=$){if(!existsSync(e))return {exists:false,schema:null,upgradedBy:null};let t=null;try{return t=new De(e,{readOnly:!0}),{exists:!0,schema:ve(t),upgradedBy:ke(t).upgradedBy}}catch{return {exists:true,schema:null,upgradedBy:null}}finally{t?.close();}}function Bt(e=$){let t=Re(e),n=ve(t);return n>v?(t.close(),t=Re(e,true),t.exec("PRAGMA query_only = 1"),Ae.set(t,{readOnly:true,dbSchema:n,appSchema:v,...ke(t)}),t):(n<v&&(Kt(t,n),t.exec(`PRAGMA user_version = ${v}`)),t)}function Ue(e,t){let n=Ie(e);if(n)throw new x(n);e.exec("BEGIN IMMEDIATE");try{let s=t();return e.exec("COMMIT"),s}catch(s){try{e.exec("ROLLBACK");}catch{}throw s}}function rs(e,t=$){let n=Bt(t);try{return e(n)}catch(s){let r=Ie(n);throw r&&!(s instanceof x)&&/readonly database/i.test(String(s))?new x(r):s}finally{n.close();}}function Wt(e,t){try{return e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!==void 0}catch{return false}}function Kt(e,t=0){let n=Wt(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
|
+
`),zt(e,t,n),qt(e),y(e,Q,a),y(e,ee,Ht);}var Ne="v4-feedback-understood";function Gt(e,t){return e.prepare("SELECT 1 FROM schema_migrations WHERE name = ?").get(t)!==void 0}function Yt(e,t){u(e,"INSERT OR IGNORE INTO schema_migrations (name, applied_at) VALUES (?, ?)",t,new Date().toISOString());}function zt(e,t,n){for(let s 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(s);}catch{}Gt(e,Ne)||(t<4&&!n&&e.exec("UPDATE lessons SET feedback = 'understood' WHERE feedback = 'dont_know'"),Yt(e,Ne));}function qt(e){for(let[t,n]of Object.entries(jt))u(e,"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",t,n);}var Me="(id, timestamp, topic_id, categories, title, level, summary, body, task_context, project, repository, branch, commit_hash, folder, repository_platform, starred, feedback, imported, shared_by)",Fe="(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function $e(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 os(e,t){u(e,`INSERT OR REPLACE INTO lessons ${Me} VALUES ${Fe}`,...$e(t));}function oe(e,t){return u(e,`INSERT OR IGNORE INTO lessons ${Me} VALUES ${Fe}`,...$e(t))>0}var Jt=[["project","project"],["repository","repository"],["branch","branch"],["commit","commit_hash"]];function Vt(e,t){if(e.date_from==null&&e.date_to==null){let n=gn(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 je(e){let t=[],n=[],s=(o,...i)=>{t.push(o),n.push(...i);};Vt(e,s),e.category!=null&&s("categories LIKE ?",`%"${e.category}"%`),e.level!=null&&s("level = ?",e.level);for(let[o,i]of Jt){let a=e[o];a!=null&&s(`${i} LIKE ?`,`%${a}%`);}if(e.starred!=null&&s("starred = ?",e.starred?1:0),e.imported!=null&&s("imported = ?",e.imported?1:0),e.shared_by!=null&&s("imported = 1 AND shared_by = ?",e.shared_by),e.search!=null)for(let o of e.search.split(/\s+/).filter(Boolean)){let i=`%${o}%`;s("(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"?s("feedback IS NULL"):e.feedback!=null&&s("feedback = ?",e.feedback),{where:t.length?`WHERE ${t.join(" AND ")}`:"",params:n}}function ie(e,t={}){let{where:n,params:s}=je(t),r=E(e,`SELECT COUNT(*) AS n FROM lessons ${n}`,...s);return Number(r?.n??0)}var Zt=new Set(["timestamp","level","topic_id","title","feedback"]),Qt=new Set(["project","repository","branch","commit_hash","repository_platform"]);function is(e,t={}){let{where:n,params:s}=je(t),r=t.sort&&Zt.has(t.sort)?t.sort:"timestamp",o=(t.order??"desc").toLowerCase()==="asc"?"ASC":"DESC",i=`SELECT * FROM lessons ${n} ORDER BY ${r} ${o}`,a=[...s];if(t.page!=null){let l=t.per_page??25;i+=" LIMIT ? OFFSET ?",a.push(l,(t.page-1)*l);}return h(e,i,...a).map(ge)}function as(e,t){return u(e,"DELETE FROM lessons WHERE id = ?",t)>0}function cs(e,t){if(t.length===0)return 0;let n=t.map(()=>"?").join(", ");return u(e,`DELETE FROM lessons WHERE id IN (${n})`,...t)}function ls(e,t,n){return u(e,"UPDATE lessons SET starred = ? WHERE id = ?",n?1:0,t)>0}function Pe(e,t,n){u(e,"UPDATE lessons SET feedback = ? WHERE id = ?",n||null,t);let s=E(e,"SELECT topic_id FROM lessons WHERE id = ?",t);return s?s.topic_id:null}function en(e){return h(e,"SELECT * FROM lessons ORDER BY timestamp DESC").map(ge)}function ae(e,t){let n=0,s=0,r=0;for(let o of t){let i;try{i=O(o);}catch{r+=1;continue}oe(e,i)?n+=1:s+=1;}return {inserted:n,duplicated:s,invalid:r}}function us(e,t){if(!Qt.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(s=>s[t])}function ps(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 He(e,t){let n=E(e,"SELECT * FROM lessons WHERE id = ?",t);return n?ge(n):null}function ds(e){return h(e,"SELECT DISTINCT value FROM lessons, json_each(lessons.categories) ORDER BY value").map(n=>n.value)}function Xe(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 Be(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 ce(e){return h(e,"SELECT topic, confidence FROM knowledge ORDER BY topic").map(t=>({topic:t.topic,confidence:Number(t.confidence)}))}function le(e){let t=h(e,"SELECT group_name FROM knowledge_group_names ORDER BY group_name").map(s=>s.group_name),n=new Map;for(let s of h(e,"SELECT group_name, topic FROM knowledge_groups ORDER BY group_name, topic")){let r=s.group_name,o=n.get(r)??[];o.push(s.topic),n.set(r,o);}return t.map(s=>({name:s,topics:n.get(s)??[]}))}function F(e,t,n){let s=Math.max(0,Math.min(10,n)),r=new Date().toISOString();u(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,s,r);}function ms(e,t){let n=u(e,"DELETE FROM knowledge WHERE topic = ?",t);return u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),n>0}function Oe(e,t){let n=t.trim();if(!n)throw new Error("Group name must not be empty");return u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n)>0}function gs(e,t){return u(e,"DELETE FROM knowledge_groups WHERE group_name = ?",t),u(e,"DELETE FROM knowledge_group_names WHERE group_name = ?",t)>0}function tn(e,t,n){u(e,"INSERT OR IGNORE INTO knowledge_group_names (group_name) VALUES (?)",n),u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t),u(e,"INSERT OR IGNORE INTO knowledge_groups (group_name, topic) VALUES (?, ?)",n,t);}function fs(e,t){u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t);}function D(e){let t={};for(let I of h(e,"SELECT key, value FROM settings"))t[I.key]=I.value;let n=t.min_gap_minutes,s=t.min_hours_between,r;n!==void 0?r=Number.parseInt(n,10):s!==void 0?r=Number.parseInt(s,10)*60:r=240;let o=t.max_per_day,i=t.ui_theme??"system",a=i==="dark"||i==="light"||i==="system"?i:"system",l=t.ui_home??"auto",p=l==="lessons"||l==="knowledge"||l==="auto"?l:"auto",d=t.nudge_every,m=d!==void 0?Number.parseInt(d,10):10,_=t.nudge_scope==="global"?"global":"session";return {max_per_day:o!==void 0?Number.parseInt(o,10):2,min_gap_minutes:r,ui_theme:a,ui_home:p,nudge_every:Number.isFinite(m)&&m>=0?m:10,nudge_scope:_,share_name:t.share_name?.trim()||null}}function y(e,t,n){u(e,"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",t,n);}function We(e,t,n){let s=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()),r=Number(s?.interactions??0);return r===1&&u(e,"DELETE FROM nudge_state WHERE session_id NOT IN (SELECT session_id FROM nudge_state ORDER BY updated_at DESC LIMIT ?)",Pt),n==="global"?ue(e,t,n):r}function hs(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 ue(e,t,n){let s=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(s?.n??0)}function Es(e){u(e,"DELETE FROM nudge_state"),u(e,"UPDATE cue_state SET pending = 0 WHERE id = 1");}var pe=3;function de(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 Ke(e){u(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()),u(e,"DELETE FROM nudge_state");}function _s(e,t){u(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),u(e,"DELETE FROM nudge_state");}function me(e){let t=E(e,"SELECT COUNT(*) AS n FROM knowledge");return {knowledge_ready:Number(t?.n??0)>0}}function Ss(e){let t={};for(let n of ["project","repository","branch","repository_platform"]){let s=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]=s?s.v:null;}return t}function Ge(e){return Y.parse({...e,prerequisites:JSON.parse(String(e.prerequisites??"[]"))})}function nn(e){return G.parse(e)}function sn(e,t){u(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 rn(e,t){let n=E(e,"SELECT * FROM courses WHERE id = ?",t);return n?Ge(n):null}function on(e,t={}){let n=[],s=[];t.status!=null&&(n.push("status = ?"),s.push(t.status)),t.lesson_id!=null&&(n.push("lesson_id = ?"),s.push(t.lesson_id));let r=`SELECT * FROM courses ${n.length?`WHERE ${n.join(" AND ")}`:""} ORDER BY updated_at DESC`;return h(e,r,...s).map(Ge)}function an(e,t){return h(e,"SELECT * FROM course_steps WHERE course_id = ? ORDER BY position",t).map(nn)}function ys(e,t,n){u(e,"UPDATE course_steps SET position = position + ? WHERE course_id = ? AND position >= ?",1e5,t,n),u(e,"UPDATE course_steps SET position = position - ? WHERE course_id = ? AND position >= ?",99999,t,1e5);}function cn(e,t){u(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 Ts(e,t,n,s,r){return u(e,"UPDATE course_steps SET status = ?, done_at = ? WHERE course_id = ? AND position = ?",s,r,t,n)>0}function bs(e,t,n,s){return u(e,"UPDATE courses SET status = ?, updated_at = ?, completed_at = CASE WHEN ? = 'completed' THEN ? ELSE NULL END WHERE id = ?",n,s,n,s,t)>0}function Ls(e,t,n){u(e,"UPDATE courses SET updated_at = ? WHERE id = ?",n,t);}function Rs(e,t){return u(e,"DELETE FROM course_steps WHERE course_id = ?",t),u(e,"DELETE FROM courses WHERE id = ?",t)>0}function Ns(e){return E(e,"SELECT 1 AS one FROM courses WHERE status = 'active' LIMIT 1")!==void 0}function Os(e){let t=D(e),n=en(e),s=ce(e),r=le(e),o=new Map;for(let p of r)for(let d of p.topics)o.set(d,p.name);let i={groups:r.map(p=>p.name),topics:s.map(p=>({topic:p.topic,confidence:p.confidence,group:o.get(p.topic)??null}))},a={[z]:strToU8(JSON.stringify(t,null,2)),[q]:strToU8(JSON.stringify(n,null,2)),[J]:strToU8(JSON.stringify(i,null,2))};existsSync(U)&&(a[V]=strToU8(readFileSync(U,"utf8")));let l=on(e);if(l.length){let p=l.flatMap(d=>an(e,d.id));a[Z]=strToU8(JSON.stringify({courses:l,steps:p},null,2));for(let d of l){let m=join(Ce,d.id,M);try{let _=lstatSync(m);_.isFile()&&_.size<=te&&(a[`courses/${d.id}/${M}`]=strToU8(be(readFileSync(m,"utf8"))));}catch{}}}return zipSync(a)}var te=2*1024*1024;function ln(e,t,n){if(!(z in t))return;let s=JSON.parse(strFromU8(t[z]));"max_per_day"in s&&y(e,"max_per_day",String(s.max_per_day)),"min_gap_minutes"in s&&y(e,"min_gap_minutes",String(s.min_gap_minutes)),(s.ui_theme==="system"||s.ui_theme==="dark"||s.ui_theme==="light")&&y(e,"ui_theme",s.ui_theme),(s.ui_home==="auto"||s.ui_home==="lessons"||s.ui_home==="knowledge")&&y(e,"ui_home",s.ui_home),"nudge_every"in s&&y(e,"nudge_every",String(s.nudge_every)),(s.nudge_scope==="session"||s.nudge_scope==="global")&&y(e,"nudge_scope",s.nudge_scope),typeof s.share_name=="string"&&y(e,"share_name",$t(s.share_name)),n.settings=1;}function un(e,t,n){if(!(J in t))return;let s=JSON.parse(strFromU8(t[J]));if(!(s&&typeof s=="object"&&Array.isArray(s.topics))){for(let[o,i]of Object.entries(s))F(e,o,i);n.topics=Object.keys(s).length;return}let r=0;for(let o of s.groups??[])Oe(e,o)&&(r+=1);for(let o of s.topics){F(e,o.topic,o.confidence);let i=o.group;i&&i!=="Other"&&(Oe(e,i)&&(r+=1),tn(e,o.topic,i));}n.topics=s.topics.length,n.groups=r;}function pn(e,t,n){if(!(q in t))return;let s=JSON.parse(strFromU8(t[q])),{inserted:r,duplicated:o,invalid:i}=ae(e,s);n.lessons=r,n.skipped=o,n.invalid=i;}function dn(e,t){V in e&&(mkdirSync(dirname(U),{recursive:true}),writeFileSync(U,strFromU8(e[V]),"utf8"),t.learning_state=1);}function mn(e,t,n){if(!(Z in t))return;let s=JSON.parse(strFromU8(t[Z])),r=new Set;for(let o of s.courses??[])try{let i=Y.parse(o);rn(e,i.id)||(sn(e,i),n.courses+=1),r.add(i.id);}catch{n.invalid+=1;}for(let o of s.steps??[])try{let i=G.parse(o);if(!r.has(i.course_id))continue;E(e,"SELECT 1 AS one FROM course_steps WHERE course_id = ? AND position = ?",i.course_id,i.position)||cn(e,i);}catch{n.invalid+=1;}for(let[o,i]of Object.entries(t)){let a=Mt.exec(o)?.[1];if(!a||!r.has(a)||i.length>te)continue;let l=join(Ce,a),p=join(l,M);if(existsSync(p))continue;let d=Te(strFromU8(i)),m=d!==null&&Buffer.byteLength(d)<=te;mkdirSync(l,{recursive:true}),writeFileSync(p,m?d:i);}}function xs(e,t){let n={settings:0,topics:0,groups:0,lessons:0,skipped:0,invalid:0,learning_state:0,courses:0},s=unzipSync(t);return ln(e,s,n),un(e,s,n),pn(e,s,n),dn(s,n),mn(e,s,n),n}function gn(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 ge(e){let t=[];try{let n=JSON.parse(e.categories);Array.isArray(n)&&(t=n);}catch{t=[];}return O({...e,categories:t,starred:!!e.starred,imported:!!e.imported})}function Ye(e){let t=[...e].length,n=Math.max(34-t-2,2),s=Math.floor(n/2);return `### ${"\u2500".repeat(s)} ${e} ${"\u2500".repeat(n-s)}`}function ze(e){let t=e.level.charAt(0).toUpperCase()+e.level.slice(1),n=e.categories.join(" \xB7 ");return [Ye("\u{1F393} devcoach"),`**${e.title}** \xB7 ${n} \xB7 ${t}`,"",e.body?.trim()||e.summary,"",Ye(`${e.topic_id} \xB7 ${e.level}`)].join(`
|
|
112
|
+
`)}var X="devcoach.lesson",A=1,Sn=`devcoach:lesson:${A}:`,yn="https://ultimaphoenix.github.io/dev-coach/lesson",Tn=".devcoach.md",bn=256*1024,f=class extends Error{},C=z$1.string().nullish(),Ln=["task_context","project","branch","commit_hash"],Rn=new Set(["github","gitlab","bitbucket"]),Je=z$1.object({format:z$1.literal(X),version:z$1.literal(A),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:B,task_context:C,project:C,repository:C,branch:C,commit_hash:C,repository_platform:W.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 Us(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 s of Ln)e[s]!=null&&(n[s]=e[s]);e.repository_platform&&Rn.has(e.repository_platform)&&(e.repository!=null&&(n.repository=e.repository),n.repository_platform=e.repository_platform);}return {format:X,version:A,lesson:n,origin:{id:e.id,timestamp:e.timestamp},shared_by:t.sharedBy,shared_at:K(t.now??new Date),app_version:a}}function Ms(e){return e.anonymous?null:e.explicit!=null?e.explicit.trim()||null:e.setting?.trim()||e.gitUserName?.trim()||null}function Ve(e){let t=deflateSync(strToU8(JSON.stringify(e)),{level:9});return Sn+Buffer.from(t).toString("base64url")}var Ze=/devcoach:lesson:(\d+):([A-Za-z0-9_-]+)/,Nn=64e3,fe=512,H=class extends Error{};function On(e,t){let n=[],s=0,r=new Inflate(a=>{if(s+=a.length,s>t)throw new H("too large");n.push(a);});for(let a=0;a<e.length;a+=fe)r.push(e.subarray(a,a+fe),a+fe>=e.length);let o=new Uint8Array(s),i=0;for(let a of n)o.set(a,i),i+=a.length;return o}function xn(e){let t=new RegExp(`^${Ze.source}$`).exec(e.trim());if(!t)throw new f("That is not a devcoach lesson code.");if(Number(t[1])!==A)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>Nn)throw new f("This lesson code is too large to be a lesson.");let s;try{let r=On(new Uint8Array(Buffer.from(n,"base64url")),bn);s=strFromU8(r);}catch(r){throw r 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 Qe(s)}function Qe(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=Je.safeParse(t);if(!n.success)throw new f("This is not a valid devcoach lesson.");return n.data}function Fs(e,t=Ve(e)){let n=e.shared_by?`Shared by ${e.shared_by} with devcoach`:"Shared with devcoach";return [ze(e.lesson),"",`${n} \u2014 paste it to your agent or run: devcoach import`,t].join(`
|
|
113
|
+
`)}function $s(e,t=Ve(e)){return `${yn}#${t}`}function wn(e,t="lesson"){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||t}function js(e){return `${wn(e.origin.id)}${Tn}`}var Dn=/^[A-Za-z0-9_.+:@/-]+$/,P=e=>Dn.test(e)?e:JSON.stringify(e),Cn=e=>`[${e.map(P).join(", ")}]`;function Ps(e){let t=e.lesson,n=["---",`format: ${X}`,`version: ${A}`,`title: ${JSON.stringify(t.title)}`,`topic_id: ${P(t.topic_id)}`,`level: ${t.level}`,`categories: ${Cn(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 s of ["task_context","project","repository","branch","commit_hash","repository_platform"]){let r=t[s];r!=null&&n.push(`${s}: ${JSON.stringify(r)}`);}return n.push("---","",t.body??t.summary,""),n.join(`
|
|
114
|
+
`)}function An(e){let t=[],n="",s=false;for(let r=0;r<e.length;r++){let o=e[r]??"";s?(n+=o,o==="\\"?n+=e[++r]??"":o==='"'&&(s=false)):o==='"'?(s=true,n+=o):o===","?(t.push(n),n=""):n+=o;}return t.push(n),t}function et(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?An(n).map(s=>et(s)):[]}return t}function In(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,l)=>l>0&&a.trim()==="---");if(n===-1)throw new f("Frontmatter never closes (missing ---).");let s={};for(let a of t.slice(1,n)){if(!a.trim()||a.trimStart().startsWith("#"))continue;let l=a.indexOf(":");if(l===-1)throw new f(`Cannot read frontmatter line: ${a.trim()}`);try{s[a.slice(0,l).trim()]=et(a.slice(l+1));}catch{throw new f(`Cannot read frontmatter line: ${a.trim()}`)}}let r=t.slice(n+1).join(`
|
|
117
|
+
`).trim(),o={format:s.format,version:typeof s.version=="string"?Number(s.version):s.version,lesson:{title:s.title,summary:s.summary,body:r||null,topic_id:s.topic_id,categories:s.categories,level:s.level,task_context:s.task_context??void 0,project:s.project??void 0,repository:s.repository??void 0,branch:s.branch??void 0,commit_hash:s.commit_hash??void 0,repository_platform:s.repository_platform??void 0},origin:{id:s.origin_id,timestamp:s.origin_timestamp},shared_by:s.shared_by??null,shared_at:s.shared_at,app_version:s.app_version},i=Je.safeParse(o);if(!i.success)throw new f("This file is not a valid devcoach lesson.");return i.data}function tt(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 r=qe(t);if(Array.isArray(r))return {kind:"lessons",records:r}}else if(t.startsWith("{")){let r=qe(t);if(r&&typeof r=="object")return r.format===X?{kind:"shared",source:"json",payload:Qe(t)}:{kind:"lessons",records:[r]}}else if(t.startsWith("---"))return {kind:"shared",source:"markdown",payload:In(t)};let n=[...t.matchAll(new RegExp(Ze.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 s;for(let r of n.reverse()){let o=[r[0]],i=kn(t.slice(r.index??0));i!==r[0]&&o.push(i);for(let a of o)try{return {kind:"shared",source:"code",payload:xn(a)}}catch(l){s=l;}}throw s}function kn(e){let t=e.split(`
|
|
118
|
+
`).map(s=>s.trim()),n=t[0]??"";for(let s of t.slice(1)){if(!/^[A-Za-z0-9_-]+$/.test(s))break;n+=s;}return n}function qe(e){try{return JSON.parse(e)}catch{throw new f("This looks like JSON but does not parse \u2014 copy it again.")}}function nt(e,t,n=new Date){let s=e.lesson;return O({id:t,timestamp:n,topic_id:s.topic_id,categories:s.categories,title:s.title,level:s.level,summary:s.summary,body:s.body,task_context:s.task_context??null,project:s.project??null,repository:s.repository??null,branch:s.branch??null,commit_hash:s.commit_hash??null,folder:null,repository_platform:s.repository_platform??null,starred:false,feedback:null,imported:true,shared_by:e.shared_by})}var vn=50;function st(e,t=vn){let n=e.origin.id,s=[n];for(let r=1;s.length<t;r++)s.push(r===1?`${n}-shared`:`${n}-shared-${r}`);return s}function rt(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)}function ot(e){try{let t=D(e),n=new Date,s=new Date(n.getTime()-24*36e5).toISOString(),r=j(e,s);if(r>=t.max_per_day)return {allowed:!1,reason:`Daily limit reached (${r}/${t.max_per_day} lessons in the last 24h)`};let o=Be(e);if(o!=null){let i=new Date(o),a=(n.getTime()-i.getTime())/6e4;if(a<t.min_gap_minutes){let l=t.min_gap_minutes-a,p=Math.floor(t.min_gap_minutes/60),d=t.min_gap_minutes%60,m=Math.trunc(l),_=Math.floor(m/60),I=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 (${_}h ${I}m remaining)`}}}return {allowed:!0}}catch(t){return {allowed:true,reason:`Rate limit check failed: ${t}`}}}function Ws(e){try{return {knowledge:ce(e),groups:le(e)}}catch{return {knowledge:[],groups:[]}}}function Un(e,t,n){let s=w(e)[t]??5,r=Math.max(0,Math.min(10,s+n));return F(e,t,r),r}function Ks(e,t,n){return Ue(e,()=>{let s=e.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(t);if(!s)return null;let r=s.feedback??null,o=w(e);if(r===n)return {topic_id:s.topic_id,previous:r,delta:0,confidence:o[s.topic_id]??null};Pe(e,t,n);let i=(n==="know"?1:0)-(r==="know"?1:0),a=i!==0?Un(e,s.topic_id,i):o[s.topic_id]??null;return {topic_id:s.topic_id,previous:r,delta:i,confidence:a}})}function Mn(e){try{let t=new Date,n=ie(e),s=new Date(t.getTime()-24*36e5).toISOString(),r=new Date(t.getTime()-7*864e5).toISOString(),o=j(e,s),i=j(e,r),a=w(e),l=Object.entries(a).sort((m,_)=>m[1]-_[1]),p=l.slice(0,5).map(([m,_])=>({topic:m,confidence:_})),d=l.slice(-5).reverse().map(([m,_])=>({topic:m,confidence:_}));return {total_lessons:n,lessons_today:o,lessons_this_week:i,imported_lessons:ie(e,{imported:!0}),weakest_topics:p,strongest_topics:d}}catch(t){return {error:String(t)}}}function Fn(e,t){let n=Object.hasOwn(w(e),t.lesson.topic_id);for(let s of st(t)){let r=He(e,s);if(r){if(rt(r,t))return {kind:"shared",inserted:0,duplicated:1,invalid:0,lesson:r,topic_tracked:n};continue}let o=nt(t,s);if(oe(e,o))return {kind:"shared",inserted:1,duplicated:0,invalid:0,lesson:o,topic_tracked:n}}return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:n}}function Gs(e,t){let n=tt(t);return n.kind==="lessons"?{kind:"lessons",...ae(e,n.records),lesson:null,topic_tracked:null}:Fn(e,n.payload)}function Ys(e){try{return Xe(e)}catch{return []}}function zs(e,t,n){let s=a=>({cue:false,reason:a,nextLessonNumber:0});if(n.planMode)return s("plan mode (not counted)");if(!me(e).knowledge_ready)return s("onboarding not complete");let r=D(e),o="pacing disabled (nudge_every=0)";if(r.nudge_every>0){let a=We(e,t??"__nosession__",r.nudge_scope),{pending:l}=de(e),p=l?Math.min(pe,r.nudge_every):r.nudge_every;if(o=`${a}/${p}${l?" (retry window)":""}`,a<p)return s(`paced (${o})`)}let i=ot(e);return i.allowed?(Ke(e),{cue:true,reason:`cue (${o})`,nextLessonNumber:Number(Mn(e).total_lessons??0)+1}):s(`rate limited: ${i.reason??"denied"}`)}function qs(e,t){try{let n=[],s=!0;if(!me(e).knowledge_ready)return {wouldCue:!1,reasons:["onboarding not complete \u2014 no knowledge topics yet"]};let r=D(e);if(r.nudge_every>0){let i=ue(e,t??"__nosession__",r.nudge_scope)+1,{pending:a}=de(e),l=a?Math.min(pe,r.nudge_every):r.nudge_every;i<l?(s=!1,n.push(`pacing: next stop would be ${i}/${l}${a?" (retry window)":""}`)):n.push(`pacing: next stop reaches the threshold (${i}/${l})`);}else n.push("pacing disabled (nudge_every=0) \u2014 every eligible stop cues");let o=ot(e);return o.allowed?n.push("rate limit: allowed"):(s=!1,n.push(`rate limited: ${o.reason??"denied"}`)),{wouldCue:s,reasons:n}}catch(n){return {wouldCue:false,reasons:[`check failed: ${n}`]}}}export{de as $,Ie as A,Ps as Aa,ke as B,ot as Ba,ss as C,Ws as Ca,Ue as D,Un as Da,rs as E,Ks as Ea,os as F,Mn as Fa,ie as G,Gs as Ga,is as H,Ys as Ha,as as I,zs as Ia,cs as J,qs as Ja,ls as K,en as L,us as M,ps as N,He as O,ds as P,ce as Q,F as R,ms as S,Oe as T,gs as U,tn as V,fs as W,D as X,y as Y,hs as Z,Es as _,ye as a,_s as aa,ft as b,me as ba,ht as c,Ss as ca,Te as d,sn as da,B as e,rn as ea,W as f,on as fa,Et as g,an as ga,Wn as h,ys as ha,Rt as i,cn as ia,Nt as j,Ts as ja,Ot as k,bs as ka,xt as l,Ls as la,wt as m,Rs as ma,Dt as n,Ns as na,O as o,Os as oa,re as p,te as pa,$ as q,xs as qa,U as r,f as ra,Ce as s,Us as sa,M as t,Ms as ta,ns as u,Ve as ua,Ft as v,xn as va,$t as w,Fs as wa,v as x,$s as xa,Xt as y,wn as ya,x as z,js as za};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {q,E as E$1,A,ba,na,Ja,p,D,Ia}from'./chunk-4UBIR363.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
|
+
`);}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
|
+
`),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
|
+
|
|
6
|
+
${w[o].cue} NOW and follow it exactly \u2014 the skill is the single source of truth for the entire flow, including the explicit no-op when the completed work does not warrant a lesson.
|
|
7
|
+
${i}
|
|
8
|
+
|
|
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
|
+
|
|
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=Ia(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
|
+
- 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
|
+
- 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)&&Ja(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
|
+
`),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};
|
|
@@ -0,0 +1,2 @@
|
|
|
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-4UBIR363.js';import {a}from'./chunk-SOL7WJIT.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,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {e,g,b,d as d$1}from'./chunk-LOJI43UC.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-LOJI43UC.js';import {a as a$1,d as d$2,e as e$1,g as g$2,b as b$2,f as f$1}from'./chunk-QU6UQJWC.js';import {c as c$1,b as b$1,a as a$2,e as e$4,f as f$2}from'./chunk-MRABMPXO.js';import {e as e$3,f,b as b$3,a as a$3}from'./chunk-3XARTIBH.js';import {h,m,g as g$1,e as e$2,d as d$3,n,o,p as p$1,q as q$1}from'./chunk-O5DVS7P7.js';import {E,A as A$1,Ca,H as H$1,O,K as K$1,I as I$1,Ea,X,ta,Y,w,sa,za,Aa,xa,wa,ua,Ga,ra,Ba,Fa,v,R,V,S,T,U as U$1,W,oa,Q,L,r,qa,p,q,y,B as B$1,x,ba,Z,$,Ja}from'./chunk-4UBIR363.js';import {a}from'./chunk-SOL7WJIT.js';import {spawn,spawnSync,execFileSync}from'child_process';import {writeFileSync,existsSync,readFileSync,rmSync,mkdirSync,realpathSync,openSync,readSync,closeSync,statSync,accessSync,constants}from'fs';import {createInterface}from'readline/promises';import {text}from'stream/consumers';import {Command}from'commander';import {homedir,platform}from'os';import {join,dirname}from'path';var j=e=>e==="win32",oe=e=>e.includes("/")||e.includes("\\");function pt(e,o){return e.endsWith("/")||e.endsWith("\\")?e+o:`${e}${e.includes("\\")&&!e.includes("/")?"\\":"/"}${o}`}function Ao(e,o){try{return statSync(e).isFile()?(j(o)||accessSync(e,constants.X_OK),!0):!1}catch{return false}}function B(e,o={}){let n=o.platform??process.platform,r=j(n)?/\.[a-z0-9]+$/i.test(e)?[""]:(o.pathext??process.env.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean).map(i=>i.toLowerCase()):[""];if(oe(e)){for(let i of r)if(Ao(e+i,n))return e+i;return null}let s=o.path??process.env.PATH??"";for(let i of s.split(j(n)?";":":"))if(i)for(let l of r){let a=pt(i,e+l);if(Ao(a,n))return a}return null}function gt(e){let o=null;try{o=openSync(e,"r");let n=Buffer.alloc(256),r=readSync(o,n,0,n.length,0),s=n.subarray(0,r).toString("utf8");return s.startsWith("#!")&&s.slice(2).split(/\r?\n/)[0]?.trim()||null}catch{return null}finally{o!==null&&closeSync(o);}}var Ho={command:"npx",args:["-y","devcoach","mcp"]};function Ro(e){let{hit:o,entry:n,execPath:r,platform:s}=e;if(!o)return Ho;let i=b$3(j(s)?n:o,s);if(i.ephemeral||b$3(n,s).ephemeral)return Ho;let l=i.boundTo?`bound to Node ${i.boundTo} (${i.manager}) \u2014 after removing or switching that version, run \`devcoach install --force\` again`:void 0;return j(s)?{command:r,args:[n,"mcp"],note:l}:{command:o,args:["mcp"],note:l}}function Mo(e=process.platform,o=process.env,n=homedir()){if(e==="darwin")return join(n,"Library","Logs","Claude","mcp-server-devcoach.log");if(e==="win32"){let r=o.APPDATA??join(n,"AppData","Roaming");return join(r,"Claude","logs","mcp-server-devcoach.log")}return join(o.XDG_CONFIG_HOME??join(n,".config"),"Claude","logs","mcp-server-devcoach.log")}function To(e,o=process.platform){let n=e.split(/\r?\n/),r=null;for(let i=n.length-1;i>=0&&r===null;i--){if(!/Using MCP server command:/.test(n[i]??""))continue;let l=[];for(let a=i+1;a<n.length&&a<i+80;a++){let u=n[a]??"";if(/\[length\]:/.test(u)||/^\S/.test(u))break;let g=/^\s*'(.+)',?\s*$/.exec(u);g?.[1]&&l.push(g[1].replace(/\\\\/g,"\\"));}l.length&&(r=l.join(j(o)?";":":"));}let s=null;for(let i=n.length-1;i>=0&&s===null;i--){let l=n[i]??"";if(/^Failed to spawn process/.test(l))s=l.trim();else if(/\[error\]/.test(l)){let a=(n[i-1]??"").trim();s=/^Failed to spawn process/.test(a)?a:l.replace(/\s*\{ metadata:.*$/,"").replace(/^\S+Z \[devcoach\] \[error\] /,"").trim();}}return {path:r,lastError:s}}function ft(e,o){let n=spawnSync(e,o,{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"&&/\.(cmd|bat)$/i.test(e)});return n.status===0?(n.stdout??"").trim():null}var ye="`Failed to spawn process: No such file or directory`";function we(e,o){let n=o.platform??process.platform,r=o.run??ft,s=[],{command:i}=e,l=e.args??[];if(i==="npx")return s.push({level:"warn",text:"starts via `npx -y devcoach` \u2014 needs the npx cache (or the network) at every start"}),s;let a$1=B(i,{...o,platform:n});if(!a$1)return s.push({level:"bad",text:`\`${i}\` is not found ${oe(i)?"":`on ${o.pathSource} `}\u2014 the host fails with ${ye}. Reinstall devcoach, then run \`devcoach install --force\``}),s;s.push({level:"ok",text:`command resolves to ${a$1}${oe(i)?"":` on ${o.pathSource}`}`}),oe(i)||s.push({level:"warn",text:"bare name \u2014 the host resolves it on its own PATH, which can differ from your shell's; `devcoach install --force` writes an absolute path"});let u=/(^|[/\\])node(\.exe)?$/i.test(a$1),g=u?l[0]:a$1;if(u&&(!g||!existsSync(g)))return s.push({level:"bad",text:`devcoach's script ${g??"(missing)"} does not exist`}),s;let p=b$3(g??a$1,n);p.ephemeral?s.push({level:"bad",text:"lives in a temporary location (npx cache or an fnm shell) \u2014 it will disappear; run `devcoach install --force`"}):p.boundTo&&s.push({level:"warn",text:`bound to Node ${p.boundTo} (${p.manager}) \u2014 removing or switching that version breaks it with ${ye}; run \`devcoach install --force\` after`});let m=u?a$1:null;if(!u&&/\.(cmd|bat)$/i.test(a$1)){if(m=B("node",{...o,platform:n}),!m)return s.push({level:"bad",text:`the \`${a$1}\` shim needs \`node\`, not found on ${o.pathSource}`}),s}else if(!u){let f=gt(a$1);if(f){let[k,...y]=f.split(/\s+/),S=/(^|\/)env$/.test(k??""),O=S?y.find(z=>!z.startsWith("-")):k;if(m=O?S?B(O,{...o,platform:n}):O:null,!m||!S&&!existsSync(m))return s.push({level:"bad",text:S?`its \`#!${f}\` finds no \`${O}\` on ${o.pathSource} \u2014 the host fails before devcoach starts`:`its interpreter ${O} does not exist \u2014 the host fails with ${ye}. Reinstall that Node (e.g. \`brew install node\`) or devcoach`}),s}}if(m){let f=r(m,["--version"]),k=f?Number.parseInt(f.replace(/^v/,"").split(".")[0]??"0",10):null;k!==null&&k<a$3?s.push({level:"bad",text:`runs on Node ${f} (${m}) \u2014 devcoach needs Node ${a$3} or newer`}):f&&s.push({level:"ok",text:`runs on Node ${f.replace(/^v/,"")} (${m})`});}let $=(u?r(a$1,[g,"--version"]):r(a$1,["--version"]))?.match(/(\d+\.\d+\.\d+[^\s]*)/)?.[1]??null;return $?$!==a?s.push({level:"warn",text:`it runs devcoach ${$}; this doctor is ${a}`}):s.push({level:"ok",text:`it runs devcoach ${$}`}):s.push({level:"warn",text:"could not run it to read its version"}),s}var c=(e="")=>{console.log(e);};function se(){let e=platform();if(e==="darwin")return join(homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let n=process.env.APPDATA??join(homedir(),"AppData","Roaming");return join(n,"Claude","claude_desktop_config.json")}let o=process.env.XDG_CONFIG_HOME??join(homedir(),".config");return join(o,"Claude","claude_desktop_config.json")}var N=join(homedir(),".claude","settings.json"),ie=join(homedir(),".claude","skills","devcoach"),De=join(homedir(),".gemini"),I=join(De,"settings.json"),yt=join(De,"extensions","devcoach","gemini-extension.json"),Ie=join(homedir(),".codex"),A=join(Ie,"hooks.json"),ne=join(Ie,"config.toml"),H=join(homedir(),".agents","skills","devcoach"),Oe=e=>B(e),Ne=e=>Oe(e)!==null;function wt(){try{return realpathSync(process.argv[1]??"")}catch{return process.argv[1]??""}}function Uo(){return Ro({hit:Oe("devcoach"),entry:wt(),execPath:process.execPath,platform:process.platform})}function Pe(){let e=Oe("devcoach");return !e||e.includes("/_npx/")||e.includes("\\_npx\\")?"npx -y devcoach":e.includes("fnm_multishells")?"devcoach":e.includes(" ")?`"${e}"`:e}function jo(e,o,n){if(!Ne(e))return "";n&&spawnSync(e,n,{encoding:"utf8"});let r=spawnSync(e,o,{encoding:"utf8"});return r.status===0?`${a$1.green("\u2713")} Registered via \`${e} mcp add\``:`${r.stderr??""}${r.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:`${a$1.red(`${e} mcp add failed:`)} ${(r.stderr||r.stdout||"").trim()}`}function Ct(e,o){if(!Ne("claude"))return "";o&&spawnSync("claude",["mcp","remove","--scope",e,"devcoach"],{encoding:"utf8"});let n=Uo(),r=spawnSync("claude",["mcp","add","--scope",e,"devcoach",n.command,"--",...n.args],{encoding:"utf8"});return r.status===0?`${a$1.green("\u2713")} Registered via \`claude mcp add\` (scope: ${e})`:`${r.stderr??""}${r.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in Claude Code (use --force to overwrite)`:`${a$1.red("claude mcp add failed:")} ${(r.stderr||r.stdout||"").trim()}`}function C(e){if(!existsSync(e))return {ok:true,data:{}};try{return {ok:!0,data:JSON.parse(readFileSync(e,"utf8"))??{}}}catch{return {ok:false,error:`${a$1.red("\u2717")} ${e} is not valid JSON \u2014 fix it and re-run (left unchanged)`}}}function Ce(e,o,n){let r=C(e);if(!r.ok)return r.error;let s=r.data;s.mcpServers??={};let i=s.mcpServers;return i.devcoach&&!n?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:(i.devcoach=o,mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(s,null,2)}
|
|
3
|
+
`),`${a$1.green("\u2713")} Installed into ${e}`)}function St(){let e=Pe();return {Stop:{hooks:[{type:"command",command:`${e} stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} prompt-hook`,timeout:30}]}}}function xt(){let e=Pe();return {AfterAgent:{hooks:[{type:"command",command:`${e} gemini-stop-hook`,timeout:6e4}]},BeforeAgent:{hooks:[{type:"command",command:`${e} gemini-prompt-hook`,timeout:3e4}]}}}function _t(){let e=Pe();return {Stop:{hooks:[{type:"command",command:`${e} codex-stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} codex-prompt-hook`,timeout:30}]}}}function Jo(e){return Object.entries(e.enabledPlugins??{}).filter(([o,n])=>n&&o.startsWith("devcoach@")).map(([o])=>o)}function Ee(e){return Jo(e).length>0}function ae(){return existsSync(yt)}function de(e){return /dev-?coach/i.test(e)&&/\b(?:stop-hook|prompt-hook|onboard-hook|lesson-ready)\b/.test(e)}function Ae(e,o){let n=C(e);if(!n.ok)return n.error;let r=n.data;r.hooks??={};let s=r.hooks,i=false;for(let[l,a]of Object.entries(o)){s[l]??=[];let u=s[l],g=u.map((m,h)=>[m,h]).filter(([m])=>(m.hooks??[]).some(h=>de(h.command??""))).map(([,m])=>m),[p]=g;if(!(g.length===1&&p!==void 0&&JSON.stringify(u[p])===JSON.stringify(a))){for(let m of g.toReversed())u.splice(m,1);u.push(a),i=true;}}return i?(mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(r,null,2)}
|
|
4
|
+
`),`${a$1.green("\u2713")} Hooks installed into ${e} (${Object.keys(o).join(" + ")})`):`${a$1.yellow("Already installed")} in ${e} (current layout)`}function Lt(){let e=C(N);return e.ok?Ee(e.data)?`${a$1.yellow("Skipped")} \u2014 the devcoach plugin already provides the hooks (installing both would double-count interactions)`:Ae(N,St()):e.error}function Dt(){return ae()?`${a$1.yellow("Skipped")} \u2014 the devcoach Gemini extension already provides the hooks (installing both would double-count interactions)`:Ae(I,xt())}function K(e){if(!existsSync(join(e,"SKILL.md")))return "missing";try{if(readFileSync(join(e,".devcoach-version"),"utf8").trim()===a)return "current"}catch{}return "outdated"}function Le(e,o){if(!o&&K(e)==="current")return `${a$1.yellow("Already installed")} in ${e} (current version)`;mkdirSync(e,{recursive:true}),writeFileSync(join(e,"SKILL.md"),e$4());let n=f$2();if(n.length){let r=join(e,"references");mkdirSync(r,{recursive:true});for(let s of n)writeFileSync(join(r,s.name),s.content);}return writeFileSync(join(e,".devcoach-version"),`${a}
|
|
5
|
+
`),`${a$1.green("\u2713")} Installed into ${e}`}function It(e){return ae()?`${a$1.yellow("Skipped")} \u2014 bundled with the devcoach Gemini extension`:Le(H,e)}function U(e,o){let n=C(e);return n.ok&&(n.data.hooks?.[o]??[]).some(r=>(r.hooks??[]).some(s=>de(s.command??"")))}function He(){let e=[{name:"Claude Code",dir:ie,cmd:"devcoach install",hooked:()=>U(N,"Stop")},{name:"Gemini/Codex",dir:H,cmd:"devcoach install --gemini (or --codex)",hooked:()=>U(I,"AfterAgent")||U(A,"Stop")}];for(let o of e){let n=K(o.dir);if(n==="outdated")return `${a$1.yellow("\u2192")} The devcoach ${o.name} skill is out of date \u2014 run ${a$1.bold(o.cmd)} to refresh it.`;if(n==="missing"&&o.hooked())return `${a$1.yellow("\u2192")} The devcoach ${o.name} skill is not installed \u2014 run ${a$1.bold(o.cmd)} to add it.`}return ""}function Ko(e){let o=e.claudeCode||e.claudeDesktop||e.gemini||e.codex,n=e.claudeCode||!o,r=e.claudeDesktop||!o,s=Uo(),i={command:s.command,args:s.args},l=false;if(c(a$1.bold("Setting up devcoach")+a$1.dim(` (${i.command} ${i.args.join(" ")})`)),s.note&&c(a$1.yellow(` ${s.note}`)),c(),n){c(a$1.bold("Claude Code"));let a=Ct("user",e.force);if(!a){let u=join(homedir(),".claude.json");a=Ce(u,{type:"stdio",env:{},...i},e.force),l=true;}c(` MCP server\u2026 ${a}`),e.skipHook||c(` Hooks\u2026 ${Lt()}`),c(` Skill\u2026 ${Le(ie,e.force)}`),c();}if(r&&(c(a$1.bold("Claude Desktop")),c(` MCP server\u2026 ${Ce(se(),i,e.force)}`),l=true,c()),e.gemini){c(a$1.bold("Gemini CLI (beta)"));let a=jo("gemini",["mcp","add","-s","user","devcoach",i.command,...i.args],e.force?["mcp","remove","-s","user","devcoach"]:null);a||(a=Ce(I,i,e.force)),c(` MCP server\u2026 ${a}`),e.skipHook||c(` Hooks\u2026 ${Dt()}`),c(` Skill\u2026 ${It(e.force)}`),c();}if(e.codex){c(a$1.bold("Codex CLI (beta)"));let a=jo("codex",["mcp","add","devcoach","--",i.command,...i.args],e.force?["mcp","remove","devcoach"]:null);a||(a=`${a$1.yellow("Manual step")} \u2014 codex CLI not found. Add to ${ne}:
|
|
6
|
+
[mcp_servers.devcoach]
|
|
7
|
+
command = "${i.command}"
|
|
8
|
+
args = [${i.args.map(u=>`"${u}"`).join(", ")}]`),c(` MCP server\u2026 ${a}`),e.skipHook||c(` Hooks\u2026 ${Ae(A,_t())}`),c(` Skill\u2026 ${Le(H,e.force)}`),c(` ${a$1.yellow("\u2192")} Codex asks once to trust new hooks \u2014 approve devcoach on the next run.`),c();}l&&c(`${a$1.yellow("\u2192")} Restart Claude Desktop to pick up the new server.
|
|
9
|
+
`),c(a$1.dim(`Tip: run devcoach backup to export your profile, lessons and settings.
|
|
10
|
+
run devcoach restore <file> to import a backup on a new machine.
|
|
11
|
+
After upgrading devcoach (npm, Homebrew), re-run devcoach install to refresh the skill.`));}function Re(e){let o=[];for(let[n,r]of Object.entries(e.hooks??{}))for(let s of r??[])for(let i of s.hooks??[])de(i.command??"")&&o.push({event:n,cmd:i});return o}function Me(e,o,n){for(let{cmd:r}of o){let s=r.command.split(" ")[0]??"";s.startsWith("/")&&!existsSync(s)&&e.bad(`hook command not found: ${s} \u2014 re-run ${a$1.bold(n)}`),r.command.startsWith("npx ")&&e.warn("hook runs via npx \u2014 needs the npx cache (or network) on every stop"),r.timeout==null&&e.warn(`hook entry has no timeout (${r.command})`);}}function zo(e){let o=e;if(!o||typeof o.command!="string")return null;let n=Array.isArray(o.args)?o.args.filter(r=>typeof r=="string"):[];return {command:o.command,args:n}}function Vo(e,o){for(let n of o)e[n.level](n.text);}function Ot(e){c(a$1.bold(`
|
|
12
|
+
Claude Desktop`));let o=se(),n=C(o);if(!n.ok){e.bad(`${o} is not valid JSON \u2014 Claude Desktop cannot read it`);return}let r=zo(n.data.mcpServers?.devcoach);if(!r){c(a$1.dim(` not registered \u2014 add it with ${a$1.bold("devcoach install --claude-desktop")}`));return}e.ok(`registered: ${[r.command,...r.args].join(" ")}`);let s=Mo(),i=existsSync(s)?To(Nt(s)):{path:null,lastError:null};Vo(e,we(r,{path:i.path??void 0,pathSource:i.path?"Claude Desktop's PATH (from its log)":"this shell's PATH"})),existsSync(s)&&(c(` log: ${s}`),i.lastError&&c(` last error in it: ${i.lastError}`));}function Nt(e){try{let o=readFileSync(e,"utf8");return o.length>256*1024?o.slice(-256*1024):o}catch{return ""}}function Pt(e){c(a$1.bold(`
|
|
13
|
+
Gemini CLI wiring (beta)`));let o=C(I),n=ae();if(!o.ok){e.bad(`${I} is not valid JSON \u2014 hooks cannot run`);return}let r=Re(o.data);n&&r.length?e.bad("devcoach hooks are registered TWICE (extension + settings.json) \u2014 interactions are double-counted. Uninstall the extension or remove the settings.json entries."):n?e.ok("hooks provided by the devcoach Gemini extension"):r.length?(r.some(i=>i.event==="AfterAgent")?e.ok("AfterAgent stop hook wired"):e.warn(`no AfterAgent stop hook \u2014 run ${a$1.bold("devcoach install --gemini")}`),r.some(i=>i.event==="BeforeAgent")?e.ok("BeforeAgent priming hook wired"):e.warn(`no BeforeAgent priming hook \u2014 run ${a$1.bold("devcoach install --gemini")} (lessons land more reliably)`),Me(e,r,"devcoach install --gemini")):e.warn(`no devcoach hooks in ${I} \u2014 run ${a$1.bold("devcoach install --gemini")}`);let s=K(H);n?e.ok("skill bundled with the devcoach Gemini extension"):s==="current"?e.ok("skill installed (~/.agents/skills, current version)"):s==="outdated"?e.warn(`skill is out of date \u2014 run ${a$1.bold("devcoach install --gemini")}`):e.warn(`skill not installed \u2014 run ${a$1.bold("devcoach install --gemini")}`),o.data.mcpServers?.devcoach?e.ok("MCP server registered (settings.json)"):e.warn(`MCP server not found in settings.json \u2014 it may be registered elsewhere (check with ${a$1.bold("gemini mcp list")})`);}function Et(e){c(a$1.bold(`
|
|
14
|
+
Codex CLI wiring (beta)`));let o=C(A);if(!o.ok){e.bad(`${A} is not valid JSON \u2014 hooks cannot run`);return}let n=Re(o.data);n.length?(n.some(i=>i.event==="Stop")?e.ok("Stop hook wired"):e.warn(`no Stop hook \u2014 run ${a$1.bold("devcoach install --codex")}`),n.some(i=>i.event==="UserPromptSubmit")?e.ok("UserPromptSubmit priming hook wired"):e.warn(`no UserPromptSubmit priming hook \u2014 run ${a$1.bold("devcoach install --codex")} (lessons land more reliably)`),Me(e,n,"devcoach install --codex"),e.warn("Codex runs hooks only after you trust them once \u2014 check the prompt on the next run")):e.warn(`no devcoach hooks in ${A} \u2014 run ${a$1.bold("devcoach install --codex")}`);let r=K(H);r==="current"?e.ok("skill installed (~/.agents/skills, current version)"):r==="outdated"?e.warn(`skill is out of date \u2014 run ${a$1.bold("devcoach install --codex")}`):e.warn(`skill not installed \u2014 run ${a$1.bold("devcoach install --codex")}`);let s=existsSync(ne)?readFileSync(ne,"utf8"):"";/\[mcp_servers\.devcoach\]/.test(s)?e.ok("MCP server registered (config.toml)"):e.warn(`MCP server not found in config.toml \u2014 it may be registered elsewhere (check with ${a$1.bold("codex mcp list")})`);}function Wo(){let e={ok:a=>c(` ${a$1.green("\u2713")} ${a}`),warn:a=>c(` ${a$1.yellow("\u2192")} ${a}`),bad:a=>c(` ${a$1.red("\u2717")} ${a}`)},{ok:o,warn:n,bad:r}=e;c(`
|
|
15
|
+
${a$1.bold("devcoach doctor")} ${a$1.dim(`v${a}`)}
|
|
16
|
+
`),c(a$1.bold("Environment"));let s=e$3();Number.parseInt(process.versions.node.split(".")[0]??"0",10)>=24?o(`Node ${process.versions.node} via ${s.nodeManager} (\u2265 24)`):r(`Node ${process.versions.node} \u2014 devcoach needs Node \u2265 24 (embedded node:sqlite)`),o(`this devcoach: ${f(s.where)} (${s.entry})`),c(a$1.bold(`
|
|
17
|
+
Claude Code wiring`));let l=C(N);if(!l.ok)r(`${N} is not valid JSON \u2014 hooks cannot run`);else {let a=Ee(l.data),u=Re(l.data),g=Jo(l.data);if(g.length>1&&r(`${g.length} devcoach plugins enabled (${g.join(", ")}) \u2014 their hooks fire twice and interactions are double-counted. Keep one enabled (/plugin).`),a&&u.length)r("devcoach hooks are registered TWICE (plugin + settings.json) \u2014 interactions are double-counted. Disable the plugin or remove the settings.json entries.");else if(a)o("hooks provided by the devcoach plugin");else if(!u.length)r(`no devcoach hooks in ${N} \u2014 run ${a$1.bold("devcoach install")}`);else {let $=u.filter(({cmd:k})=>k.command.includes("onboard-hook")||k.command.includes("lesson-ready")),f=u.filter(k=>k.event==="Stop").length;$.length?n(`legacy two-entry Stop layout \u2014 run ${a$1.bold("devcoach install")} to merge into one stop-hook entry (fewer spawns per stop)`):f>1?n(`${f} devcoach Stop entries \u2014 interactions are counted ${f}\xD7 per stop; run ${a$1.bold("devcoach install")} to keep only the current one`):o(`Stop hook wired (${f} entry)`),u.some(k=>k.event==="UserPromptSubmit")?o("UserPromptSubmit priming hook wired"):n(`no UserPromptSubmit priming hook \u2014 run ${a$1.bold("devcoach install")} to add it (lessons land more reliably)`),Me(e,u,"devcoach install");}let p=K(ie);p==="current"?o("Claude Code skill installed (current version)"):a?o("Claude Code skill bundled with the plugin"):n(p==="outdated"?`Claude Code skill is out of date \u2014 run ${a$1.bold("devcoach install")}`:`Claude Code skill not installed \u2014 run ${a$1.bold("devcoach install")}`);let m=C(join(homedir(),".claude.json")),h=m.ok?zo(m.data.mcpServers?.devcoach):null;h?(o("MCP server registered (user scope)"),Vo(e,we(h,{pathSource:"this shell's PATH"}))):a||n(`MCP server not found in ~/.claude.json \u2014 it may be registered elsewhere (check with ${a$1.bold("claude mcp get devcoach")})`);}if(existsSync(se())&&Ot(e),existsSync(De)&&Pt(e),existsSync(Ie)&&Et(e),c(a$1.bold(`
|
|
18
|
+
Database & pacing`)),!existsSync(q)){n(`no database yet (${q}) \u2014 onboarding runs on the first technical task`),c();return}try{E(a=>{o(`database opens (${q})`);let u=A$1(a);if(u)r(y(u)),c(" this build is READ-ONLY on it: hooks cannot count interactions, lessons cannot be saved \u2014 update this install (brew upgrade devcoach / npm, or the plugin) or run the newer channel");else {let k=B$1(a);o(`schema v${x}`+(k.upgradedBy?`, last upgraded by devcoach ${k.upgradedBy}`:""));}ba(a).knowledge_ready?o("onboarding complete"):n("onboarding not complete \u2014 the next stop cues it");let g=X(a);c(` settings: max_per_day=${g.max_per_day} \xB7 min_gap_minutes=${g.min_gap_minutes} \xB7 nudge_every=${g.nudge_every} \xB7 nudge_scope=${g.nudge_scope}`);let p=Z(a),m=p.reduce((k,y)=>k+y.interactions,0);c(` pacing: ${p.length} session(s) counted, ${m} interaction(s) total`+(p[0]?` \u2014 latest ${p[0].session_id.slice(0,8)}\u2026 at ${p[0].interactions}`:""));let h=$(a);h.pending?n(`a cue is pending since ${h.last_cue_at} (retry threshold armed)`):h.last_skip_reason&&c(` last skip: "${h.last_skip_reason}"`);let $$1=Ba(a);$$1.allowed?o("rate limit: allowed"):n(`rate limit: ${$$1.reason}`),c(a$1.bold(`
|
|
19
|
+
Verdict`));let f=Ja(a,p[0]?.session_id??null);f.wouldCue?o("the next eligible stop WOULD cue a lesson"):n("the next eligible stop would NOT cue a lesson:");for(let k of f.reasons)c(` \xB7 ${k}`);});}catch(a){r(`database check failed: ${a}`);}c();}function Se(e){if(!existsSync(e))return `${a$1.yellow("Not registered")} (${e} does not exist)`;let o=C(e);if(!o.ok)return o.error;let n=o.data.mcpServers;return n?.devcoach?(delete n.devcoach,writeFileSync(e,`${JSON.stringify(o.data,null,2)}
|
|
20
|
+
`),`${a$1.green("\u2713")} Removed from ${e}`):`${a$1.yellow("Not registered")} in ${e}`}function xe(e,o){if(!Ne(e))return "";let n=spawnSync(e,o,{encoding:"utf8"}),r=`${n.stderr??""}${n.stdout??""}`.toLowerCase();return n.status===0?`${a$1.green("\u2713")} Removed via \`${e} mcp remove\``:/not found|no .*server|does not exist|no such/.test(r)?`${a$1.yellow("Not registered")} in ${e}`:`${a$1.red(`${e} mcp remove failed:`)} ${(n.stderr||n.stdout||"").trim()}`}function _e(e){if(!existsSync(e))return `${a$1.yellow("No hooks")} (${e} does not exist)`;let o=C(e);if(!o.ok)return o.error;let n=o.data.hooks??{},r=[];for(let[s,i]of Object.entries(n)){if(!i)continue;let l=i.filter(a=>!(a.hooks??[]).some(u=>de(u.command??"")));l.length!==i.length&&(r.push(s),l.length?n[s]=l:delete n[s]);}return r.length===0?`${a$1.yellow("No devcoach hooks")} in ${e}`:(Object.keys(n).length===0&&delete o.data.hooks,writeFileSync(e,`${JSON.stringify(o.data,null,2)}
|
|
21
|
+
`),`${a$1.green("\u2713")} Hooks removed from ${e} (${r.join(" + ")})`)}function Go(e){return existsSync(join(e,"SKILL.md"))?(rmSync(e,{recursive:true,force:true}),`${a$1.green("\u2713")} Removed ${e}`):`${a$1.yellow("Not installed")} (${e})`}async function At(e){if(e)return true;if(!process.stdin.isTTY)return c(`${a$1.yellow("\u2192")} Not a terminal \u2014 pass --yes to delete ${p} without a prompt.`),false;let o=createInterface({input:process.stdin,output:process.stdout});try{let n=await o.question(`Delete ${p} \u2014 every lesson, your profile and the notebook? [y/N] `);return /^y(es)?$/i.test(n.trim())}finally{o.close();}}async function Xo(e){let o=e.claudeCode||e.claudeDesktop||e.gemini||e.codex,n=e.all||e.claudeCode||!o,r=e.all||e.claudeDesktop||!o,s=e.all||e.gemini,i=e.all||e.codex;if(c(a$1.bold("Removing devcoach from your agents")),c(),n){c(a$1.bold("Claude Code"));let l=C(N);l.ok&&Ee(l.data)&&c(` ${a$1.yellow("\u2192")} The devcoach plugin is enabled \u2014 its hooks, skill and MCP server go away with /plugin uninstall devcoach; only user-level entries are removed here.`);let a=xe("claude",["mcp","remove","--scope","user","devcoach"]);a||(a=Se(join(homedir(),".claude.json"))),c(` MCP server\u2026 ${a}`),c(` Hooks\u2026 ${_e(N)}`),c(` Skill\u2026 ${Go(ie)}`),c();}if(r&&(c(a$1.bold("Claude Desktop")),c(` MCP server\u2026 ${Se(se())}`),c(` ${a$1.yellow("\u2192")} Restart Claude Desktop to drop the server.`),c()),s){c(a$1.bold("Gemini CLI (beta)"));let l=xe("gemini",["mcp","remove","-s","user","devcoach"]);l||(l=Se(I)),c(` MCP server\u2026 ${l}`),c(` Hooks\u2026 ${_e(I)}`),ae()&&c(` ${a$1.yellow("\u2192")} The devcoach Gemini extension is installed \u2014 remove it with: gemini extensions uninstall devcoach`),c();}if(i){c(a$1.bold("Codex CLI (beta)"));let l=xe("codex",["mcp","remove","devcoach"]);l||(l=`${a$1.yellow("Manual step")} \u2014 codex CLI not found. Delete the [mcp_servers.devcoach] table from ${ne}`),c(` MCP server\u2026 ${l}`),c(` Hooks\u2026 ${_e(A)}`),c();}if(s||i){let l=!s&&U(I,"AfterAgent")||!i&&U(A,"Stop");c(a$1.bold("Shared skill (Gemini + Codex)")),c(` Skill\u2026 ${l?`${a$1.yellow("Kept")} \u2014 still used by the other client (${H})`:Go(H)}`),c();}e.data?(c(a$1.bold("Coaching data")),existsSync(p)?await At(e.yes)?(rmSync(p,{recursive:true,force:true}),c(` ${a$1.green("\u2713")} Deleted ${p}`)):c(` ${a$1.yellow("Kept")} ${p}`):c(` ${a$1.yellow("Nothing to delete")} (${p} does not exist)`),c()):c(a$1.dim(`Your lessons, profile and notebook are still in ${p} \u2014 keep them for a
|
|
22
|
+
reinstall, or delete them with: devcoach uninstall --data (devcoach backup first).`)),c(a$1.dim(`Then remove the program itself: brew uninstall devcoach \xB7 npm uninstall -g devcoach
|
|
23
|
+
(npx: nothing to remove).`));}function Rt(e,o){return e==="darwin"?["open",[o]]:e==="win32"?["cmd",["/c","start","",o]]:["xdg-open",[o]]}function Te(e,o=process.platform){let[n,r]=Rt(o,e);try{let s=spawn(n,r,{detached:!0,stdio:"ignore"});s.on("error",()=>{}),s.unref();}catch{}}var d=(e="")=>{console.log(e);},Qo=e=>e==="junior"?a$1.green(e):e==="mid"?a$1.yellow(e):e==="senior"?a$1.red(e):e,Ft=e=>e==="know"?` ${a$1.green("\u2713")}`:e==="understood"?` ${a$1.cyan("\u{1F4A1}")}`:e==="dont_know"?` ${a$1.red("\u2717")}`:"",et=e=>e==="know"?a$1.green("\u2713 I knew this"):e==="understood"?a$1.cyan("\u{1F4A1} Understood"):e==="dont_know"?a$1.red("\u2717 Couldn't follow"):a$1.dim("no feedback"),Yo=["know","understood","dont_know","clear"];function ot(e,o){let n=new Map;for(let s of e.groups)for(let i of s.topics)n.set(i,s.name);let r=[...e.knowledge].sort((s,i)=>i.confidence-s.confidence).map(s=>{let i=g$2(s.confidence);return [s.topic,n.get(s.topic)??"Other",b$2(i,`${s.confidence}/10`),b$2(i,f$1(s.confidence))]});return d$2(o,[{header:"Topic"},{header:"Group"},{header:"Confidence",justify:"right"},{header:"Bar"}],r)}function Ge(e){e&&d(a$1.yellow(`\u2192 ${y(e)}`));}function Bt(){let{profile:e,compat:o}=E(n=>({profile:Ca(n),compat:A$1(n)}));d(ot(e,"Knowledge Map")),Ge(o);}function Ut(e){let o=null,n=E(a=>(o=A$1(a),H$1(a,{period:e.period==="all"?null:e.period,category:e.category,level:e.level,project:e.project,repository:e.repository,branch:e.branch,commit:e.commit,starred:e.starred?true:null,imported:e.imported||e.sharedBy?true:null,shared_by:e.sharedBy,feedback:e.feedback,date_from:e.dateFrom,date_to:e.dateTo,sort:e.sort,order:e.order})));if(n.length===0){d(a$1.dim("No lessons found."));return}let r=n.some(a=>a.project||a.branch||a.commit_hash),s=[{header:""},{header:"Date"},{header:"Topic"},{header:"Title"},{header:"Level",justify:"center"},{header:"Categories"}];r&&s.push({header:"Project"},{header:"Branch"},{header:"Commit"});let i=n.some(a=>a.imported);i&&s.push({header:"Shared by"});let l=n.map(a=>{let u=[a.starred?a$1.yellow("\u2605"):a$1.dim("\xB7"),a.timestamp.slice(0,10),a$1.cyan(a.topic_id),a.title+Ft(a.feedback),Qo(a.level),a.categories.join(", ")];return r&&u.push(a.project??"",a.branch?a$1.magenta(a.branch):"",a.commit_hash?a$1.cyan(a.commit_hash.slice(0,7)):""),i&&u.push(a.imported?a.shared_by??a$1.dim("anonymous"):""),u});d(d$2("Lessons",s,l)),Ge(o);}function Jt(e){let o=E(r=>O(r,e));o===null&&(d(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),d(e$1(a$1.bold(o.title))),d(`${a$1.dim("ID:")} ${o.id}`),d(`${a$1.dim("Date:")} ${o.timestamp.slice(0,19).replace("T"," ")}`),d(`${a$1.dim("Topic:")} ${o.topic_id}`),d(`${a$1.dim("Categories:")} ${o.categories.join(", ")}`),d(`${a$1.dim("Level:")} ${Qo(o.level)}`);let n=o.starred?a$1.yellow("\u2605 starred"):a$1.dim("\u2606 not starred");if(d(`${a$1.dim("Star:")} ${n} ${a$1.dim("Feedback:")} ${et(o.feedback)}`),o.imported&&d(`${a$1.dim("Shared by:")} ${o.shared_by??a$1.dim("anonymous")}`),o.task_context&&d(`${a$1.dim("Context:")} ${o.task_context}`),o.project||o.repository||o.branch||o.commit_hash||o.folder){let r=[];o.project&&r.push(`project=${o.project}`),o.repository&&r.push(`repo=${o.repository}`),o.branch&&r.push(`branch=${a$1.magenta(o.branch)}`),o.commit_hash&&r.push(`commit=${a$1.cyan(o.commit_hash.slice(0,7))}`),o.folder&&r.push(`folder=${o.folder}`),d(`${a$1.dim("Git:")} ${r.join(" \xB7 ")}`);}d(e$1()),d(o.summary);}function Zo(e,o){E(r=>O(r,e)===null?false:(K$1(r,e,o),true))||(d(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),d(o?`Lesson ${a$1.cyan(e)} \u2192 ${a$1.yellow("\u2605 starred")}`:`Lesson ${a$1.cyan(e)} \u2192 ${a$1.dim("\u2606 unstarred")}`);}function Kt(e){E(n=>I$1(n,e))||(d(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),d(`Lesson ${a$1.cyan(e)} deleted.`);}var tt=e=>e==="completed"?a$1.green(e):e==="abandoned"?a$1.dim(e):a$1.cyan(e);function zt(){let e=E(r=>h(r));if(e.length===0){d(a$1.dim("No courses yet. Start one in your agent: /devcoach:course, or say yes after a \u274C."));return}let o=[{header:"ID"},{header:"Title"},{header:"Progress",justify:"center"},{header:"Status"},{header:"Updated"}],n=e.map(r=>{let s=m(r);return [a$1.cyan(r.id),r.title,`${s.done}/${s.total}`,tt(r.status),r.updated_at.slice(0,10)]});d(d$2("Courses",o,n));}function Vt(e){let o=E(s=>g$1(s,e));o===null&&(d(a$1.red(`Course '${e}' not found.`)),process.exit(1));let n=m(o);d(e$1(a$1.bold(o.title))),d(`${a$1.dim("ID:")} ${o.id}`),d(`${a$1.dim("Topic:")} ${a$1.cyan(o.topic_id)} ${a$1.dim("Status:")} ${tt(o.status)} ${a$1.dim("Progress:")} ${n.done}/${n.total}`),o.goal&&d(`${a$1.dim("Goal:")} ${o.goal}`),o.lesson_id&&d(`${a$1.dim("From:")} lesson ${a$1.cyan(o.lesson_id)}`);let r=e$2(o.id);if(d(`${a$1.dim("Document:")} ${r??a$1.dim(`not written yet \u2192 ${d$3(o.id)}`)}`),o.prerequisites.length&&d(`${a$1.dim("Chain:")} ${o.prerequisites.map(s=>s.known?a$1.green(`\u2713 ${s.concept}`):a$1.red(`\u2717 ${s.concept}`)).join(a$1.dim(" \u2192 "))}`),d(""),o.steps.length===0){d(a$1.dim("No steps registered yet."));return}for(let s of o.steps){let i=s.status==="done"?a$1.green("\u25CF"):s.status==="skipped"?a$1.dim("\u25CC"):a$1.cyan("\u25CB");d(` ${i} ${s.position}. ${s.title} ${a$1.dim(`${s.kind} \xB7 #${s.anchor}${s.status!=="todo"?` \xB7 ${s.status}`:""}`)}`);}}function Wt(e,o){Yo.includes(o)||(d(a$1.red(`Invalid feedback '${o}'. Use: ${Yo.join(" | ")}`)),process.exit(1));let n=o==="clear"?null:o,r=E(i=>Ea(i,e,n));r===null&&(d(a$1.red(`Lesson '${e}' not found.`)),process.exit(1));let s;if(n===null)s="feedback cleared";else if(r.delta!==0&&r.confidence!==null){let i=r.confidence-r.delta;s=`${a$1.cyan(r.topic_id)} confidence: ${i} \u2192 ${a$1.bold(String(r.confidence))}`;}else s=`${a$1.cyan(r.topic_id)} confidence unchanged`;d(`Lesson ${a$1.cyan(e)} \u2192 ${et(n)} (${s})`);}function Xt(){let e=E(s=>X(s)),o=Math.floor(e.min_gap_minutes/60),n=e.min_gap_minutes%60,r=o?`${o}h ${n}m`:`${n}m`;d(d$2("Settings",[{header:"Key"},{header:"Value",justify:"right"}],[["max_per_day",String(e.max_per_day)],["min_gap_minutes",`${e.min_gap_minutes} (${r})`],["nudge_every",e.nudge_every===0?"0 (off)":String(e.nudge_every)],["nudge_scope",e.nudge_scope],["share_name",e.share_name??a$1.dim("(unset \u2192 git user.name)")]]));}function Yt(){let{stats:e,rateLimit:o,settings:n,compat:r}=E(g=>({stats:Fa(g),rateLimit:Ba(g),settings:X(g),compat:A$1(g)})),s=g=>Number(e[g]??0),i=o.allowed?a$1.green("Available now"):a$1.yellow(o.reason??"");d(d$2("Coaching Stats",[{header:"Metric"},{header:"Value",justify:"right"}],[["Total lessons",String(s("total_lessons"))],["Lessons today (24h)",`${s("lessons_today")} / ${n.max_per_day}`],["Lessons this week",String(s("lessons_this_week"))],["Next lesson",i]],false));let l=e.weakest_topics??[],a=e.strongest_topics??[];if(l.length||a.length){let g=[];for(let p=0;p<Math.max(l.length,a.length);p++){let m=l[p],h=a[p];g.push([m?`${a$1.red(m.topic)} ${a$1.dim(`(${m.confidence})`)}`:"",h?`${a$1.green(h.topic)} ${a$1.dim(`(${h.confidence})`)}`:""]);}d(d$2(void 0,[{header:"Weakest topics"},{header:"Strongest topics"}],g));}let u=He();u&&d(`
|
|
24
|
+
${u}`),Ge(r);}function Zt(e,o){let n=["max_per_day","min_gap_minutes","nudge_every","nudge_scope","share_name"];if(n.includes(e)||(d(a$1.red(`Unknown key '${e}'. Valid keys: ${n.join(", ")}`)),process.exit(1)),e==="nudge_scope"&&o!=="session"&&o!=="global"&&(d(a$1.red(`Invalid nudge_scope '${o}'. Use: session | global`)),process.exit(1)),e==="share_name"){o.trim().length>v&&(d(a$1.red(`share_name must be at most ${v} characters.`)),process.exit(1));let r=w(o);E(s=>Y(s,e,r)),d(r?a$1.green(`Set share_name = ${r}`):a$1.green("Cleared share_name"));return}if(e==="nudge_every"){let r=Number.parseInt(o,10);(!Number.isInteger(r)||r<0)&&(d(a$1.red(`Invalid nudge_every '${o}'. Use a non-negative integer (0 = off).`)),process.exit(1));}E(r=>Y(r,e,o)),d(a$1.green(`Set ${e} = ${o}`));}function qt(e,o,n){let r=e.trim();r||(d(a$1.red("Topic name must not be empty.")),process.exit(1)),E(i=>{R(i,r,o),n&&n!=="Other"&&V(i,r,n);});let s=n&&n!=="Other"?` \u2192 ${a$1.cyan(n)}`:"";d(`${a$1.green("Added")} ${a$1.bold(r)} (confidence ${a$1.cyan(String(o))})${s}`);}function Qt(e){let o=E(n=>S(n,e));d(o?`${a$1.green("Removed")} ${a$1.bold(e)} from knowledge map`:a$1.yellow(`Topic '${e}' not found.`));}function en(e){let o=e.trim();(!o||o==="Other")&&(d(a$1.red("Invalid group name.")),process.exit(1)),E(n=>T(n,o)),d(`${a$1.green(`Group '${a$1.cyan(o)}' ready.`)} Assign topics with: devcoach group-assign <topic> "${o}"`);}function on(e){let o=E(n=>U$1(n,e));d(o?`${a$1.green(`Removed group '${a$1.cyan(e)}'`)}`:a$1.yellow(`Group '${e}' not found.`));}function tn(e,o){E(r=>r.prepare("SELECT topic FROM knowledge WHERE topic = ?").get(e)===void 0?false:(o==="Other"?W(r,e):V(r,e,o),true))||(d(a$1.red(`Topic '${e}' not in knowledge map. Add it first.`)),process.exit(1)),d(o==="Other"?`${a$1.green("Moved")} ${a$1.bold(e)} \u2192 Other (ungrouped)`:`${a$1.green("Moved")} ${a$1.bold(e)} \u2192 ${a$1.cyan(o)}`);}function nn(e){let{lessonsCount:o,knowledgeCount:n,data:r$1}=E(i=>({lessonsCount:L(i).length,knowledgeCount:Q(i).length,data:oa(i)}));writeFileSync(e,r$1);let s=existsSync(r)?" + notebook":"";d(`${a$1.green("Backup saved:")} ${e} (${a$1.cyan(String(o))} lessons, ${a$1.cyan(String(n))} topics${s})`);}function rn(e){existsSync(e)||(d(a$1.red(`File not found: ${e}`)),process.exit(1));let o=E(r=>qa(r,readFileSync(e)));o.settings&&d(`${a$1.green("\u2713")} Settings restored`),o.topics&&d(`${a$1.green("\u2713")} Knowledge map restored (${a$1.cyan(String(o.topics))} topics)`);let n=[`${a$1.cyan(String(o.lessons))} imported`];o.skipped&&n.push(`${a$1.yellow(String(o.skipped))} duplicates skipped`),o.invalid&&n.push(`${a$1.red(String(o.invalid))} rejected (invalid)`),d(`${a$1.green("\u2713")} Lessons: ${n.join(", ")}`),o.learning_state&&d(`${a$1.green("\u2713")} Notebook restored`);}var ce=e=>{console.error(a$1.dim(e));};function sn(e,o){o.by!==void 0&&o.anonymous&&(d(a$1.red("Use either --by <name> or --anonymous, not both.")),process.exit(1)),!e&&!o.last&&(d(a$1.red("Give a lesson id, or --last for the most recent one.")),process.exit(2));let n$1=E(i=>{let l=o.last?H$1(i,{imported:false,page:1,per_page:1})[0]??null:O(i,e);if(!l)return null;let a=X(i),u=ta({explicit:o.by??null,anonymous:o.anonymous,setting:a.share_name,gitUserName:n()}),g=false;return o.by?.trim()&&!a.share_name&&(Y(i,"share_name",w(o.by)),g=true),{payload:sa(l,{includeContext:!!o.withContext,sharedBy:u}),remembered:g}});n$1||(d(a$1.red(o.last?"No lessons to share yet.":`Lesson '${e}' not found.`)),process.exit(1));let{payload:r,remembered:s}=n$1;if(o.file!==void 0&&o.file!==false){let i=typeof o.file=="string"?o.file:za(r);writeFileSync(i,Aa(r)),d(`${a$1.green("Saved:")} ${i}`);}else o.link?d(xa(r)):(d(wa(r)),ua(r).length>16e3&&ce("Long lesson \u2014 prefer --file for a friendlier hand-off."));s&&ce("(remembered as share_name \u2014 change it with: devcoach set share_name <name>)"),r.shared_by===null&&ce("Shared anonymously (set a name with: devcoach set share_name <name>)");}function an(){let e=process.platform==="darwin"?[["pbpaste",[]]]:process.platform==="win32"?[["powershell",["-NoProfile","-Command","Get-Clipboard"]]]:[["wl-paste",["--no-newline"]],["xclip",["-selection","clipboard","-o"]],["xsel",["--clipboard","--output"]]];for(let[o,n]of e)try{let r=execFileSync(o,n,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]});if(r.trim())return r}catch{}throw new ra("Clipboard is empty or unavailable \u2014 pass the code, a link or a file, or pipe it: devcoach import -")}async function dn(e){return e===void 0?an():e==="-"?text(process.stdin):existsSync(e)?readFileSync(e,"utf8"):p$1(e)&&!e.includes("#devcoach:lesson:")?q$1(e):e}async function cn(e){try{let o=await dn(e),n=E(i=>Ga(i,o));if(n.kind==="lessons"){let i=[`${a$1.cyan(String(n.inserted))} imported`];n.duplicated&&i.push(`${a$1.yellow(String(n.duplicated))} duplicates skipped`),n.invalid&&i.push(`${a$1.red(String(n.invalid))} rejected (invalid)`),d(`${a$1.green("\u2713")} Lessons: ${i.join(", ")}`);return}let r=n.lesson;r||(d(a$1.red("Could not store the lesson.")),process.exit(1));let s=r.shared_by?`shared by ${r.shared_by}`:"shared anonymously";n.duplicated?d(`${a$1.yellow("Already in your log")} as ${a$1.cyan(r.id)} (${s})`):d(`${a$1.green("\u2713 Imported")} ${a$1.bold(`"${r.title}"`)} (${s}) as ${a$1.cyan(r.id)} \u2014 topic ${a$1.cyan(r.topic_id)}`),n.topic_tracked===!1&&ce(`Topic '${r.topic_id}' is not in your knowledge map \u2014 track it with: devcoach knowledge-add ${r.topic_id}`);}catch(o){if(!(o instanceof ra))throw o;d(a$1.red(o.message)),process.exit(1);}}async function ln(){let e=createInterface({input:process.stdin,output:process.stdout}),o$1=async(r,s="")=>{let i=s?` [${s}]`:"";return (await e.question(`${r}${i}: `)).trim()||s},n=async(r,s,i,l)=>{for(;;){let a=await o$1(r,String(s)),u=Number.parseInt(a,10);if(!Number.isNaN(u)&&u>=i&&u<=l)return u;d(a$1.red(`Must be ${i}\u2013${l}.`));}};try{d(e$1(a$1.bold("devcoach setup"))),d(`
|
|
25
|
+
${a$1.bold("Step 1")} \u2014 Restore from backup`);let r=await o$1("Path to existing backup zip (Enter to skip)","");if(r){existsSync(r)||(d(a$1.red(`File not found: ${r}`)),process.exit(1));let p=E(m=>qa(m,readFileSync(r)));d(`${a$1.green("\u2713")} Restored: ${p.topics} topics, ${p.lessons} lessons${p.learning_state?", notebook":""}`),d(a$1.green("Setup complete!"));return}d(`
|
|
26
|
+
${a$1.bold("Step 2")} \u2014 Build your knowledge profile`);let s=(await o$1("Mode: [a]utomatic (detect from files) / [m]anual (type your stack)","a")).toLowerCase(),i={};if(s.startsWith("a")){let m=o().folder??process.cwd(),h=c$1(),$=b$1(a$2(m),h.detected_stack),f=Object.keys($).sort((y,S)=>y.localeCompare(S));if(h.scanned_projects>0){d(`
|
|
27
|
+
${a$1.dim(`Scanned ${h.scanned_projects} Claude Code project(s) from your history:`)}`);for(let y of h.projects.slice(0,8))d(a$1.dim(` ${y.name} \u2014 ${y.topics.join(", ")||"no stack signals"}`));}if(f.length){d(`
|
|
28
|
+
${a$1.dim(`Detected (history + ${a$1.cyan(m)}):`)}`);for(let y of f){let S=$[y]??5,O=await o$1(` ${a$1.cyan(y)} (Enter=keep, 0-10=override, s=skip)`,String(S));if(O.toLowerCase()==="s")continue;let z=Number.parseInt(O,10);i[y]=Number.isNaN(z)?S:Math.max(0,Math.min(10,z));}}else d(a$1.dim("No technology files detected in current directory."));let k=await o$1(`
|
|
29
|
+
Add additional topics (comma-separated, or Enter to skip)`,"");for(let y of k.split(",").map(S=>S.trim()).filter(Boolean))i[y]=await n(` Confidence for ${a$1.cyan(y)}`,5,0,10);}else for(d(a$1.dim(`
|
|
30
|
+
Enter topics: 'topic_id confidence' (e.g. python 7). Blank line when done.`));;){let p=(await o$1("Topic (Enter when done)","")).trim();if(!p)break;let m=p.split(/\s+/),h=m[0]??"",$=Number.parseInt(m[1]??"",10),f=Number.isNaN($)?5:Math.max(0,Math.min(10,$));i[h]=f,d(` ${a$1.green("+")} ${a$1.cyan(h)} \u2192 ${f}`);}let l={};if(Object.keys(i).length&&(d(`
|
|
31
|
+
${a$1.bold("Step 3")} \u2014 Organise into groups`),(await o$1("Organise topics into groups? [y/N]","n")).toLowerCase().startsWith("y"))){let m=[];for(let h of Object.keys(i).sort(($,f)=>$.localeCompare(f))){let $=m.length?m.join(", "):"(none yet)",f=await o$1(` Group for ${a$1.cyan(h)} existing: ${a$1.dim($)} (Enter=Other)`,"");if(f&&f!=="Other"){let k=l[f]??[];k.push(h),l[f]=k,m.includes(f)||m.push(f);}}}d(`
|
|
32
|
+
${a$1.bold("Step 4")} \u2014 Rate-limit settings`);let a=await n("Max lessons per day",2,1,20),u=await n("Min gap between lessons (minutes)",240,0,1440),g=E(p=>{p.exec("DELETE FROM knowledge; DELETE FROM knowledge_groups; DELETE FROM knowledge_group_names;");for(let[m,h]of Object.entries(i))R(p,m,h);for(let[m,h]of Object.entries(l))for(let $ of h)V(p,$,m);return Y(p,"max_per_day",String(a)),Y(p,"min_gap_minutes",String(u)),Ca(p)});d(ot(g,"Knowledge Profile")),d(`
|
|
33
|
+
${a$1.green("Setup complete!")} ${Object.keys(i).length} topics saved.`);}finally{e.close();}}function un(){let e=[["mcp","Start the MCP server (stdio) for Claude Code / Claude Desktop"],["ui [--port N] [--open|--stop]","Launch the web dashboard (default port: 7860)"],["setup","First-run wizard: import backup or build your knowledge profile"],["install","Register MCP server + hooks + skill (Claude default; --gemini/--codex beta)"],["uninstall","Remove the MCP server, hooks and skill (--all: every agent; --data: wipe ~/.devcoach)"],["onboard-hook","Claude Code Stop hook: cue onboarding when no profile exists"],["lesson-ready","Claude Code Stop hook: cue a lesson when one is due"],["doctor","Diagnose the agent wiring \u2014 explains why a lesson would(n't) fire"],["profile","Show the knowledge map"],["stats","Coaching statistics and rate-limit status"],["settings / set","Show / update settings (max_per_day | min_gap_minutes)"],["lessons / lesson","List past lessons / show one in detail"],["courses / course","List your courses / show one with its steps"],["star / unstar / delete","Manage a lesson"],["feedback <id>","Record know / understood / dont_know feedback"],["share / import","Hand a lesson to a teammate / add a shared lesson to your log"],["knowledge-add / -remove","Add / remove a topic"],["group-add / -remove / -assign","Manage knowledge groups"],["backup / restore","Export / import a full backup zip"]];d(`
|
|
34
|
+
${a$1.bold(`devcoach ${a$1.dim(`v${a}`)}`)}`);for(let[n,r]of e)d(` ${a$1.cyan(`devcoach ${n}`.padEnd(32))} ${a$1.dim(r)}`);d(`
|
|
35
|
+
${a$1.dim("Run")} ${a$1.bold("devcoach <command> --help")} ${a$1.dim("for per-command options.")}
|
|
36
|
+
`);let o=He();o&&d(`${o}
|
|
37
|
+
`);}function _(e){return e??null}function mn(){let e$1=new Command;return e$1.exitOverride(),e$1.name("devcoach").description("devcoach \u2014 progressive technical coaching").version(`devcoach ${a}`,"-v, --version","output the version number").allowExcessArguments(false).showHelpAfterError("(add --help for usage)").action(()=>un()),e$1.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-SF3PTN52.js');await o();}),e$1.command("profile").description("Show the knowledge map").action(Bt),e$1.command("lessons").description("List past lessons").option("--period <period>","today | week | month | year | all","all").option("--category <category>","Filter by category tag").option("--project <project>","Filter by project (fuzzy)").option("--repository <repository>","Filter by repository (fuzzy)").option("--branch <branch>","Filter by branch (fuzzy)").option("--commit <commit>","Filter by commit hash prefix (fuzzy)").option("--starred","Show only starred lessons").option("--imported","Show only lessons shared with you").option("--from <name>","Show only lessons shared by this person").option("--feedback <feedback>","know | understood | dont_know | none").option("--level <level>","junior | mid | senior").option("--date-from <date>","Show lessons on or after this date (YYYY-MM-DD[THH:MM])").option("--date-to <date>","Show lessons on or before this date (YYYY-MM-DD[THH:MM])").option("--sort <column>","timestamp | level | topic_id | title | feedback","timestamp").option("--order <order>","asc | desc","desc").action(o=>Ut({period:o.period??"all",category:_(o.category),project:_(o.project),repository:_(o.repository),branch:_(o.branch),commit:_(o.commit),starred:!!o.starred,imported:!!o.imported,sharedBy:_(o.from),feedback:_(o.feedback),level:_(o.level),dateFrom:_(o.dateFrom),dateTo:_(o.dateTo),sort:o.sort??"timestamp",order:o.order??"desc"})),e$1.command("lesson").description("Show a single lesson in detail").argument("<id>","Lesson ID").action(o=>Jt(o)),e$1.command("courses").description("List your step-by-step courses").action(()=>zt()),e$1.command("course").description("Show a course: steps, progress, document path").argument("<id>","Course ID").action(o=>Vt(o)),e$1.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Zo(o,true)),e$1.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Zo(o,false)),e$1.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>Kt(o)),e$1.command("feedback").description("Record your answer under a lesson: know / understood / dont_know").argument("<id>","Lesson ID").argument("<value>","know | understood | dont_know | clear").action((o,n)=>Wt(o,n)),e$1.command("share").description("Share a lesson: copyable text (default), --link, or a .devcoach.md file").argument("[id]","Lesson ID (or use --last)").option("--last","Share the most recent lesson").option("--link","Print the share link only").option("--file [path]","Write a .devcoach.md file (default: <lesson-id>.devcoach.md)").option("--with-context","Include project/branch/commit/task context (off by default)").option("--by <name>","Sender name (remembered as share_name when unset)").option("--anonymous","Share without a sender name").action((o,n)=>sn(o,n)),e$1.command("import").description("Import a shared lesson: a devcoach:lesson code, a share link, a URL, a .devcoach.md or lessons .json file, - for stdin \u2014 or the clipboard when omitted").argument("[source]","Code, link, URL, file path, or - (stdin); omit to read the clipboard").action(o=>cn(o)),e$1.command("settings").description("Show current settings").action(Xt),e$1.command("stats").description("Coaching statistics and rate-limit status").action(Yt),e$1.command("set").description("Update a setting (max_per_day | min_gap_minutes | nudge_every | nudge_scope | share_name)").argument("<key>","Setting key").argument("<value>","New value").action((o,n)=>Zt(o,n)),e$1.command("knowledge-add").description("Add or update a topic in the knowledge map").argument("<topic>","Topic ID").option("--confidence <n>","Initial confidence 0-10","5").option("--group <group>","Assign to a named group").action((o,n)=>qt(o,Math.max(0,Math.min(10,Number.parseInt(n.confidence??"5",10)||5)),_(n.group))),e$1.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>Qt(o)),e$1.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>en(o)),e$1.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>on(o)),e$1.command("group-assign").description("Move a topic to a group").argument("<topic>","Topic ID").argument("<group>","Group name (use 'Other' to ungroup)").action((o,n)=>tn(o,n)),e$1.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>nn(o)),e$1.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>rn(o)),e$1.command("install").description("Register the MCP server + hooks + skill in Claude Code / Claude Desktop (default), Gemini CLI (--gemini, beta), or Codex CLI (--codex, beta)").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Target Google Gemini CLI (beta)").option("--codex","Target OpenAI Codex CLI (beta)").option("--force","Overwrite existing devcoach entry").option("--skip-hook","Register MCP server only \u2014 skip the hooks").action(o=>Ko({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,force:!!o.force,skipHook:!!o.skipHook})),e$1.command("uninstall").description("Remove devcoach's MCP server, hooks and skill from Claude Code / Claude Desktop (default), Gemini CLI (--gemini), Codex CLI (--codex) or every agent (--all). Coaching data is kept unless --data is given. Run it before brew/npm uninstall.").option("--claude-code","Target Claude Code only").option("--claude-desktop","Target Claude Desktop only").option("--gemini","Also remove from Google Gemini CLI (beta)").option("--codex","Also remove from OpenAI Codex CLI (beta)").option("--all","Every agent: Claude Code, Claude Desktop, Gemini CLI, Codex CLI").option("--data","Also delete ~/.devcoach (lessons, profile, notebook) \u2014 asks first").option("--yes","Do not ask before deleting the data (with --data)").action(async o=>Xo({claudeCode:!!o.claudeCode,claudeDesktop:!!o.claudeDesktop,gemini:!!o.gemini,codex:!!o.codex,all:!!o.all,data:!!o.data,yes:!!o.yes})),e$1.command("doctor").description("Diagnose the devcoach \u21C4 agent wiring and the lesson pacing state").action(Wo),e$1.command("ui").description("Launch the web dashboard (Ctrl+C stops it gracefully; --stop stops a running one)").option("--port <port>","Port","7860").option("-o, --open","Open the dashboard in your default browser").option("--stop","Stop the dashboard running on --port (e.g. one started by your agent)").action(async o=>{let n=Number.parseInt(o.port??"7860",10)||7860,{startUi:r,stopUi:s}=await import('./app-P5HADOIH.js');if(o.stop){d(await s(n)?a$1.green(`\u2713 devcoach UI on port ${n} stopped`):a$1.yellow(`No devcoach UI is running on port ${n}`));return}r(n,{onReady:i=>{o.open&&Te(i);},onListenError:i=>{console.error(i.message),o.open&&i.existingUrl&&Te(i.existingUrl),process.exit(i.exitCode);}});}),e$1.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(ln),e$1.command("stop-hook",{hidden:true}).description("Claude Code Stop hook: onboarding check + lesson cue in one spawn").action(()=>e()),e$1.command("prompt-hook",{hidden:true}).description("Claude Code UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g()),e$1.command("gemini-stop-hook",{hidden:true}).description("Gemini CLI AfterAgent hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"gemini")),e$1.command("gemini-prompt-hook",{hidden:true}).description("Gemini CLI BeforeAgent hook: prime the model when a lesson is due").action(()=>g(void 0,"gemini")),e$1.command("codex-stop-hook",{hidden:true}).description("Codex CLI Stop hook: onboarding check + lesson cue in one spawn").action(()=>e(void 0,"codex")),e$1.command("codex-prompt-hook",{hidden:true}).description("Codex CLI UserPromptSubmit hook: prime the model when a lesson is due").action(()=>g(void 0,"codex")),e$1.command("onboard-hook",{hidden:true}).description("Claude Code Stop hook: cue onboarding (legacy two-entry layout)").action(()=>b()),e$1.command("lesson-ready",{hidden:true}).description("Claude Code Stop hook: signal a lesson is due (legacy two-entry layout)").action(()=>d$1()),e$1}async function Xn(){let e=mn();try{await e.parseAsync(process.argv);}catch(o){let n=o.code??"";if(!n.startsWith("commander."))throw o;if(n==="commander.helpDisplayed"||n==="commander.help"||n==="commander.version")return;process.exit(2);}}export{Xn as runCli};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export{c as buildLessonCue,f as buildPromptPrime,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,h as runHook}from'./chunk-
|
|
2
|
+
export{c as buildLessonCue,f as buildPromptPrime,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload,h as runHook}from'./chunk-LOJI43UC.js';import'./chunk-4UBIR363.js';import'./chunk-SOL7WJIT.js';
|