devcoach 2.6.1 → 2.6.2
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/THIRD_PARTY_NOTICES.md +26 -0
- package/assets/references/course.md +115 -25
- package/assets/static/course-frame.js +23 -5
- package/assets/static/course-viewer.js +15 -2
- package/assets/static/style.css +87 -32
- package/assets/static/vendor/highlight.LICENSE.txt +29 -0
- package/dist/{app-HPKJDE5T.js → app-L3Y3NV4O.js} +197 -154
- package/dist/bin.js +1 -1
- package/dist/chunk-QFIAOASG.js +15 -0
- package/dist/chunk-XLFZCPHN.js +2 -0
- package/dist/chunk-YMXRXYRU.js +118 -0
- package/dist/{commands-J252OWKT.js → commands-55T3DDPV.js} +13 -13
- package/dist/{hooks-5Y2QJNJJ.js → hooks-7KTW5KRO.js} +1 -1
- package/dist/server-UN3IURFP.js +9 -0
- package/package.json +3 -2
- package/dist/chunk-2OKF6FRF.js +0 -15
- package/dist/chunk-54UDP4GL.js +0 -2
- package/dist/chunk-QWZZWWAN.js +0 -111
- package/dist/server-3EYIKYWM.js +0 -9
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var r=process.argv[2]??"",e=new Set(["stop-hook","prompt-hook","gemini-stop-hook","gemini-prompt-hook","codex-stop-hook","codex-prompt-hook","onboard-hook","lesson-ready"]);try{if(e.has(r)){let{runHook:o}=await import('./hooks-
|
|
2
|
+
var r=process.argv[2]??"",e=new Set(["stop-hook","prompt-hook","gemini-stop-hook","gemini-prompt-hook","codex-stop-hook","codex-prompt-hook","onboard-hook","lesson-ready"]);try{if(e.has(r)){let{runHook:o}=await import('./hooks-7KTW5KRO.js');o(r);}else {let{runCli:o}=await import('./commands-55T3DDPV.js');await o();}}catch(o){o?.name==="SchemaTooNewError"&&(console.error(o.message),process.exit(3)),console.error(o),process.exit(1);}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {r,E as E$1,B as B$1,ba,na,Ja,q,D,Ia}from'./chunk-YMXRXYRU.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(q,{recursive:!0});let n=join(q,"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(r)){let o;try{o=E$1(t=>B$1(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(B$1(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(r)||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(r)||((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(r)||process.exit(0);let n;try{n=E$1(i=>B$1(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 {t,u,pa,da,ea,ga,fa,D,ha,ia,la,ja,ka,ma,ra,ya,e,a}from'./chunk-YMXRXYRU.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=u,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(t,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$1){try{let t=B(e$1);if(t===null)return !1;let r=e(readFileSync(t,"utf8"));return r===null||Buffer.byteLength(r)>pa?!1:(writeFileSync(t,r),!0)}catch{return false}}function et(e,t$1){let r=ya(t$1,"course"),n=r;for(let o=2;ea(e,n)!==null||existsSync(join(t,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,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
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 he={version:"2.6.2"};var N=he.version;var ct=dirname(fileURLToPath(import.meta.url)),lt='<script data-devcoach="highlighter"></script>',Ee=/<script data-devcoach="highlighter">\s*<\/script>/,ut=/<script data-devcoach="highlighter">\s*(?!<\/script>)\S/;function pt(){for(let e of ["../assets","../../assets"]){let t=join(ct,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 dt(){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 mt=';(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=pt();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">${dt()}</style>
|
|
3
|
+
<script data-devcoach="highlighter">/*
|
|
4
|
+
Syntax highlighting by highlight.js, inlined by devcoach.
|
|
5
|
+
|
|
6
|
+
${n}
|
|
7
|
+
*/
|
|
8
|
+
${t}
|
|
9
|
+
${mt}</script>`;}catch{T=null;}return T}function gt(e){return ut.test(e)}function ht(e){return /<pre[\s>][\s\S]*?<code[\s>]/.test(e)}function Te(e){if(gt(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,()=>lt)}var X=z$1.enum(["junior","mid","senior"]),B=z$1.enum(["github","gitlab","bitbucket","local"]),ft=z$1.enum(["know","understood","dont_know"]),qn=z$1.number().int().min(0).max(10);function W(e){return e.toISOString().replace(/\.\d{3}Z$/,"Z")}function Et(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 _t(e){let t=Et(e),n=new Date;return t.getTime()>n.getTime()&&(t=n),W(t)}var St=z$1.union([z$1.string(),z$1.date()]).transform((e,t)=>{try{return _t(e)}catch(n){return t.addIssue({code:"custom",message:n.message}),z$1.NEVER}}),S=z$1.string().nullish().transform(e=>e??null),yt=z$1.object({id:z$1.string(),timestamp:St,topic_id:z$1.string(),categories:z$1.array(z$1.string()),title:z$1.string(),level:X,summary:z$1.string(),body:S,task_context:S,project:S,repository:S,branch:S,commit_hash:S,folder:S,repository_platform:B.nullish().transform(e=>e??null),starred:z$1.boolean().default(false),feedback:ft.nullish().transform(e=>e??null),imported:z$1.boolean().default(false),shared_by:S}),Tt=z$1.object({topic:z$1.string(),confidence:z$1.number().int()}),bt=z$1.object({name:z$1.string(),topics:z$1.array(z$1.string())});z$1.object({knowledge:z$1.array(Tt),groups:z$1.array(bt)});var Rt=z$1.enum(["system","dark","light"]),Lt=z$1.enum(["auto","lessons","knowledge"]),Nt=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:Lt.default("auto"),nudge_every:z$1.number().int().min(0).default(10),nudge_scope:Nt.default("session"),share_name:z$1.string().nullable().default(null)});var Ot=z$1.enum(["active","completed","abandoned"]),xt=z$1.enum(["concept","example","practice","check"]),wt=z$1.enum(["todo","done","skipped"]),Dt=z$1.object({concept:z$1.string().min(1),known:z$1.boolean()}),K=z$1.object({course_id:z$1.string(),position:z$1.number().int().min(1),title:z$1.string().min(1),kind:xt,anchor:z$1.string().regex(/^[a-z0-9][a-z0-9-]*$/),status:wt.default("todo"),done_at:S}),G=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(Dt).default([]),status:Ot.default("active"),created_at:z$1.string(),updated_at:z$1.string(),completed_at:S});function O(e){return yt.parse(e)}var {DatabaseSync:Ut}=createRequire(import.meta.url)("node:sqlite"),ne=process.env.DEVCOACH_DIR??join(homedir(),".devcoach"),se=join(ne,"coaching.db"),U=join(ne,"learning-state.md"),Ce=join(ne,"courses"),Y="settings.json",z="lessons.json",q="knowledge.json",V="learning-state.md",J="courses.json",M="index.html",Mt=new RegExp(`^courses/([a-z0-9-]+)/${M.replace(".","\\.")}$`),as={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 f(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 Le(e=se,t=false){e!==":memory:"&&mkdirSync(dirname(e),{recursive:true});let n=new Ut(e,{readOnly:t});return n.exec("PRAGMA busy_timeout = 3000"),n}var v=6,Ht="2.6.0",Z="schema_app_version",Q="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 ${N} (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},Ie=new WeakMap;function Ae(e){return Ie.get(e)??null}function Bt(e){try{let t=f(e,"SELECT key, value FROM settings WHERE key IN (?, ?)",Z,Q),n=s=>t.find(o=>o.key===s)?.value??null;return {upgradedBy:n(Z),minApp:n(Q)}}catch{return {upgradedBy:null,minApp:null}}}function Wt(e){let t=e.prepare("PRAGMA user_version").get();return Number(t?.user_version??0)}function Kt(e=se){let t=Le(e),n=Wt(t);return n>v?(t.close(),t=Le(e,true),t.exec("PRAGMA query_only = 1"),Ie.set(t,{readOnly:true,dbSchema:n,appSchema:v,...Bt(t)}),t):(n<v&&(Yt(t,n),t.exec(`PRAGMA user_version = ${v}`)),t)}function ke(e,t){let n=Ae(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 cs(e,t=se){let n=Kt(t);try{return e(n)}catch(s){let o=Ae(n);throw o&&!(s instanceof x)&&/readonly database/i.test(String(s))?new x(o):s}finally{n.close();}}function Gt(e,t){try{return e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!==void 0}catch{return false}}function Yt(e,t=0){let n=Gt(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
|
+
`),Vt(e,t,n),Jt(e),y(e,Z,N),y(e,Q,Ht);}var Ne="v4-feedback-understood";function zt(e,t){return e.prepare("SELECT 1 FROM schema_migrations WHERE name = ?").get(t)!==void 0}function qt(e,t){u(e,"INSERT OR IGNORE INTO schema_migrations (name, applied_at) VALUES (?, ?)",t,new Date().toISOString());}function Vt(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{}zt(e,Ne)||(t<4&&!n&&e.exec("UPDATE lessons SET feedback = 'understood' WHERE feedback = 'dont_know'"),qt(e,Ne));}function Jt(e){for(let[t,n]of Object.entries(jt))u(e,"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",t,n);}var ve="(id, timestamp, topic_id, categories, title, level, summary, body, task_context, project, repository, branch, commit_hash, folder, repository_platform, starred, feedback, imported, shared_by)",Ue="(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";function Me(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 ls(e,t){u(e,`INSERT OR REPLACE INTO lessons ${ve} VALUES ${Ue}`,...Me(t));}function oe(e,t){return u(e,`INSERT OR IGNORE INTO lessons ${ve} VALUES ${Ue}`,...Me(t))>0}var Zt=[["project","project"],["repository","repository"],["branch","branch"],["commit","commit_hash"]];function Qt(e,t){if(e.date_from==null&&e.date_to==null){let n=fn(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 Fe(e){let t=[],n=[],s=(r,...i)=>{t.push(r),n.push(...i);};Qt(e,s),e.category!=null&&s("categories LIKE ?",`%"${e.category}"%`),e.level!=null&&s("level = ?",e.level);for(let[r,i]of Zt){let a=e[r];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 r of e.search.split(/\s+/).filter(Boolean)){let i=`%${r}%`;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 re(e,t={}){let{where:n,params:s}=Fe(t),o=E(e,`SELECT COUNT(*) AS n FROM lessons ${n}`,...s);return Number(o?.n??0)}var en=new Set(["timestamp","level","topic_id","title","feedback"]),tn=new Set(["project","repository","branch","commit_hash","repository_platform"]);function us(e,t={}){let{where:n,params:s}=Fe(t),o=t.sort&&en.has(t.sort)?t.sort:"timestamp",r=(t.order??"desc").toLowerCase()==="asc"?"ASC":"DESC",i=`SELECT * FROM lessons ${n} ORDER BY ${o} ${r}`,a=[...s];if(t.page!=null){let l=t.per_page??25;i+=" LIMIT ? OFFSET ?",a.push(l,(t.page-1)*l);}return f(e,i,...a).map(me)}function ps(e,t){return u(e,"DELETE FROM lessons WHERE id = ?",t)>0}function ds(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 ms(e,t,n){return u(e,"UPDATE lessons SET starred = ? WHERE id = ?",n?1:0,t)>0}function $e(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 nn(e){return f(e,"SELECT * FROM lessons ORDER BY timestamp DESC").map(me)}function ie(e,t){let n=0,s=0,o=0;for(let r of t){let i;try{i=O(r);}catch{o+=1;continue}oe(e,i)?n+=1:s+=1;}return {inserted:n,duplicated:s,invalid:o}}function gs(e,t){if(!tn.has(t))throw new Error(`Column not allowed: ${JSON.stringify(t)}`);return f(e,`SELECT DISTINCT ${t} FROM lessons WHERE ${t} IS NOT NULL ORDER BY ${t}`).map(s=>s[t])}function hs(e){return f(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 je(e,t){let n=E(e,"SELECT * FROM lessons WHERE id = ?",t);return n?me(n):null}function fs(e){return f(e,"SELECT DISTINCT value FROM lessons, json_each(lessons.categories) ORDER BY value").map(n=>n.value)}function Pe(e){return f(e,"SELECT DISTINCT topic_id FROM lessons").map(t=>t.topic_id)}function $(e,t){let n=E(e,"SELECT COUNT(*) AS n FROM lessons WHERE timestamp >= ? AND imported = 0",t);return Number(n?.n??0)}function He(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 f(e,"SELECT topic, confidence FROM knowledge"))t[n.topic]=Number(n.confidence);return t}function ae(e){return f(e,"SELECT topic, confidence FROM knowledge ORDER BY topic").map(t=>({topic:t.topic,confidence:Number(t.confidence)}))}function ce(e){let t=f(e,"SELECT group_name FROM knowledge_group_names ORDER BY group_name").map(s=>s.group_name),n=new Map;for(let s of f(e,"SELECT group_name, topic FROM knowledge_groups ORDER BY group_name, topic")){let o=s.group_name,r=n.get(o)??[];r.push(s.topic),n.set(o,r);}return t.map(s=>({name:s,topics:n.get(s)??[]}))}function F(e,t,n){let s=Math.max(0,Math.min(10,n)),o=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,o);}function Es(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 _s(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 sn(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 Ss(e,t){u(e,"DELETE FROM knowledge_groups WHERE topic = ?",t);}function D(e){let t={};for(let A of f(e,"SELECT key, value FROM settings"))t[A.key]=A.value;let n=t.min_gap_minutes,s=t.min_hours_between,o;n!==void 0?o=Number.parseInt(n,10):s!==void 0?o=Number.parseInt(s,10)*60:o=240;let r=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:r!==void 0?Number.parseInt(r,10):2,min_gap_minutes:o,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 Xe(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()),o=Number(s?.interactions??0);return o===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"?le(e,t,n):o}function ys(e){return f(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 le(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 Ts(e){u(e,"DELETE FROM nudge_state"),u(e,"UPDATE cue_state SET pending = 0 WHERE id = 1");}var ue=3;function pe(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 Be(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 bs(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 de(e){let t=E(e,"SELECT COUNT(*) AS n FROM knowledge");return {knowledge_ready:Number(t?.n??0)>0}}function Rs(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 We(e){return G.parse({...e,prerequisites:JSON.parse(String(e.prerequisites??"[]"))})}function on(e){return K.parse(e)}function rn(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 an(e,t){let n=E(e,"SELECT * FROM courses WHERE id = ?",t);return n?We(n):null}function cn(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 o=`SELECT * FROM courses ${n.length?`WHERE ${n.join(" AND ")}`:""} ORDER BY updated_at DESC`;return f(e,o,...s).map(We)}function ln(e,t){return f(e,"SELECT * FROM course_steps WHERE course_id = ? ORDER BY position",t).map(on)}function Ls(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 un(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 Ns(e,t,n,s,o){return u(e,"UPDATE course_steps SET status = ?, done_at = ? WHERE course_id = ? AND position = ?",s,o,t,n)>0}function Os(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 xs(e,t,n){u(e,"UPDATE courses SET updated_at = ? WHERE id = ?",n,t);}function ws(e,t){return u(e,"DELETE FROM course_steps WHERE course_id = ?",t),u(e,"DELETE FROM courses WHERE id = ?",t)>0}function Ds(e){return E(e,"SELECT 1 AS one FROM courses WHERE status = 'active' LIMIT 1")!==void 0}function Cs(e){let t=D(e),n=nn(e),s=ae(e),o=ce(e),r=new Map;for(let p of o)for(let d of p.topics)r.set(d,p.name);let i={groups:o.map(p=>p.name),topics:s.map(p=>({topic:p.topic,confidence:p.confidence,group:r.get(p.topic)??null}))},a={[Y]:strToU8(JSON.stringify(t,null,2)),[z]:strToU8(JSON.stringify(n,null,2)),[q]:strToU8(JSON.stringify(i,null,2))};existsSync(U)&&(a[V]=strToU8(readFileSync(U,"utf8")));let l=cn(e);if(l.length){let p=l.flatMap(d=>ln(e,d.id));a[J]=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<=ee&&(a[`courses/${d.id}/${M}`]=strToU8(be(readFileSync(m,"utf8"))));}catch{}}}return zipSync(a)}var ee=2*1024*1024;function pn(e,t,n){if(!(Y in t))return;let s=JSON.parse(strFromU8(t[Y]));"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 dn(e,t,n){if(!(q in t))return;let s=JSON.parse(strFromU8(t[q]));if(!(s&&typeof s=="object"&&Array.isArray(s.topics))){for(let[r,i]of Object.entries(s))F(e,r,i);n.topics=Object.keys(s).length;return}let o=0;for(let r of s.groups??[])Oe(e,r)&&(o+=1);for(let r of s.topics){F(e,r.topic,r.confidence);let i=r.group;i&&i!=="Other"&&(Oe(e,i)&&(o+=1),sn(e,r.topic,i));}n.topics=s.topics.length,n.groups=o;}function mn(e,t,n){if(!(z in t))return;let s=JSON.parse(strFromU8(t[z])),{inserted:o,duplicated:r,invalid:i}=ie(e,s);n.lessons=o,n.skipped=r,n.invalid=i;}function gn(e,t){V in e&&(mkdirSync(dirname(U),{recursive:true}),writeFileSync(U,strFromU8(e[V]),"utf8"),t.learning_state=1);}function hn(e,t,n){if(!(J in t))return;let s=JSON.parse(strFromU8(t[J])),o=new Set;for(let r of s.courses??[])try{let i=G.parse(r);an(e,i.id)||(rn(e,i),n.courses+=1),o.add(i.id);}catch{n.invalid+=1;}for(let r of s.steps??[])try{let i=K.parse(r);if(!o.has(i.course_id))continue;E(e,"SELECT 1 AS one FROM course_steps WHERE course_id = ? AND position = ?",i.course_id,i.position)||un(e,i);}catch{n.invalid+=1;}for(let[r,i]of Object.entries(t)){let a=Mt.exec(r)?.[1];if(!a||!o.has(a)||i.length>ee)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)<=ee;mkdirSync(l,{recursive:true}),writeFileSync(p,m?d:i);}}function Is(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 pn(e,s,n),dn(e,s,n),mn(e,s,n),gn(s,n),hn(e,s,n),n}function fn(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 me(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 Ke(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 Ge(e){let t=e.level.charAt(0).toUpperCase()+e.level.slice(1),n=e.categories.join(" \xB7 ");return [Ke("\u{1F393} devcoach"),`**${e.title}** \xB7 ${n} \xB7 ${t}`,"",e.body?.trim()||e.summary,"",Ke(`${e.topic_id} \xB7 ${e.level}`)].join(`
|
|
112
|
+
`)}var H="devcoach.lesson",I=1,Tn=`devcoach:lesson:${I}:`,bn="https://ultimaphoenix.github.io/dev-coach/lesson",Rn=".devcoach.md",Ln=256*1024,h=class extends Error{},C=z$1.string().nullish(),Nn=["task_context","project","branch","commit_hash"],On=new Set(["github","gitlab","bitbucket"]),ze=z$1.object({format:z$1.literal(H),version:z$1.literal(I),lesson:z$1.object({title:z$1.string().min(1),summary:z$1.string(),body:z$1.string().nullable(),topic_id:z$1.string().min(1),categories:z$1.array(z$1.string()),level:X,task_context:C,project:C,repository:C,branch:C,commit_hash:C,repository_platform:B.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 js(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 Nn)e[s]!=null&&(n[s]=e[s]);e.repository_platform&&On.has(e.repository_platform)&&(e.repository!=null&&(n.repository=e.repository),n.repository_platform=e.repository_platform);}return {format:H,version:I,lesson:n,origin:{id:e.id,timestamp:e.timestamp},shared_by:t.sharedBy,shared_at:W(t.now??new Date),app_version:N}}function Ps(e){return e.anonymous?null:e.explicit!=null?e.explicit.trim()||null:e.setting?.trim()||e.gitUserName?.trim()||null}function qe(e){let t=deflateSync(strToU8(JSON.stringify(e)),{level:9});return Tn+Buffer.from(t).toString("base64url")}var Ve=/devcoach:lesson:(\d+):([A-Za-z0-9_-]+)/,xn=64e3,ge=512,P=class extends Error{};function wn(e,t){let n=[],s=0,o=new Inflate(a=>{if(s+=a.length,s>t)throw new P("too large");n.push(a);});for(let a=0;a<e.length;a+=ge)o.push(e.subarray(a,a+ge),a+ge>=e.length);let r=new Uint8Array(s),i=0;for(let a of n)r.set(a,i),i+=a.length;return r}function Dn(e){let t=new RegExp(`^${Ve.source}$`).exec(e.trim());if(!t)throw new h("That is not a devcoach lesson code.");if(Number(t[1])!==I)throw new h(`This lesson was shared by a newer devcoach (format ${t[1]}) \u2014 upgrade to import it.`);let n=t[2]??"";if(n.length>xn)throw new h("This lesson code is too large to be a lesson.");let s;try{let o=wn(new Uint8Array(Buffer.from(n,"base64url")),Ln);s=strFromU8(o);}catch(o){throw o instanceof P?new h("This lesson code is too large to be a lesson."):new h("This lesson code is damaged or incomplete \u2014 copy it again.")}return Je(s)}function Je(e){let t;try{t=JSON.parse(e);}catch{throw new h("This lesson code is damaged or incomplete \u2014 copy it again.")}let n=ze.safeParse(t);if(!n.success)throw new h("This is not a valid devcoach lesson.");return n.data}function Hs(e,t=qe(e)){let n=e.shared_by?`Shared by ${e.shared_by} with devcoach`:"Shared with devcoach";return [Ge(e.lesson),"",`${n} \u2014 paste it to your agent or run: devcoach import`,t].join(`
|
|
113
|
+
`)}function Xs(e,t=qe(e)){return `${bn}#${t}`}function Cn(e,t="lesson"){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||t}function Bs(e){return `${Cn(e.origin.id)}${Rn}`}var In=/^[A-Za-z0-9_.+:@/-]+$/,j=e=>In.test(e)?e:JSON.stringify(e),An=e=>`[${e.map(j).join(", ")}]`;function Ws(e){let t=e.lesson,n=["---",`format: ${H}`,`version: ${I}`,`title: ${JSON.stringify(t.title)}`,`topic_id: ${j(t.topic_id)}`,`level: ${t.level}`,`categories: ${An(t.categories)}`,`summary: ${JSON.stringify(t.summary)}`,`origin_id: ${j(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: ${j(e.app_version)}`];for(let s of ["task_context","project","repository","branch","commit_hash","repository_platform"]){let o=t[s];o!=null&&n.push(`${s}: ${JSON.stringify(o)}`);}return n.push("---","",t.body??t.summary,""),n.join(`
|
|
114
|
+
`)}function kn(e){let t=[],n="",s=false;for(let o=0;o<e.length;o++){let r=e[o]??"";s?(n+=r,r==="\\"?n+=e[++o]??"":r==='"'&&(s=false)):r==='"'?(s=true,n+=r):r===","?(t.push(n),n=""):n+=r;}return t.push(n),t}function Ze(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?kn(n).map(s=>Ze(s)):[]}return t}function vn(e){let t=e.replace(/\r\n?/g,`
|
|
115
|
+
`).split(`
|
|
116
|
+
`);if(t[0]?.trim()!=="---")throw new h("Missing frontmatter (--- \u2026 ---).");let n=t.findIndex((a,l)=>l>0&&a.trim()==="---");if(n===-1)throw new h("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 h(`Cannot read frontmatter line: ${a.trim()}`);try{s[a.slice(0,l).trim()]=Ze(a.slice(l+1));}catch{throw new h(`Cannot read frontmatter line: ${a.trim()}`)}}let o=t.slice(n+1).join(`
|
|
117
|
+
`).trim(),r={format:s.format,version:typeof s.version=="string"?Number(s.version):s.version,lesson:{title:s.title,summary:s.summary,body:o||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=ze.safeParse(r);if(!i.success)throw new h("This file is not a valid devcoach lesson.");return i.data}function Qe(e){let t=e.trim();if(!t)throw new h("Nothing to import \u2014 paste a lesson code, link or file.");if(t.startsWith("[")){let o=Ye(t);if(Array.isArray(o))return {kind:"lessons",records:o}}else if(t.startsWith("{")){let o=Ye(t);if(o&&typeof o=="object")return o.format===H?{kind:"shared",source:"json",payload:Je(t)}:{kind:"lessons",records:[o]}}else if(t.startsWith("---"))return {kind:"shared",source:"markdown",payload:vn(t)};let n=[...t.matchAll(new RegExp(Ve.source,"g"))];if(n.length===0)throw new h("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 o of n.reverse()){let r=[o[0]],i=Un(t.slice(o.index??0));i!==o[0]&&r.push(i);for(let a of r)try{return {kind:"shared",source:"code",payload:Dn(a)}}catch(l){s=l;}}throw s}function Un(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 Ye(e){try{return JSON.parse(e)}catch{throw new h("This looks like JSON but does not parse \u2014 copy it again.")}}function et(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 Mn=50;function tt(e,t=Mn){let n=e.origin.id,s=[n];for(let o=1;s.length<t;o++)s.push(o===1?`${n}-shared`:`${n}-shared-${o}`);return s}function nt(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 st(e){try{let t=D(e),n=new Date,s=new Date(n.getTime()-24*36e5).toISOString(),o=$(e,s);if(o>=t.max_per_day)return {allowed:!1,reason:`Daily limit reached (${o}/${t.max_per_day} lessons in the last 24h)`};let r=He(e);if(r!=null){let i=new Date(r),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),A=m%60;return {allowed:!1,reason:`Too soon: last lesson ${a<0?`in ${Math.round(-a)}m (future timestamp)`:`${Math.round(a)}m ago`}, minimum interval is ${p}h ${d}m (${_}h ${A}m remaining)`}}}return {allowed:!0}}catch(t){return {allowed:true,reason:`Rate limit check failed: ${t}`}}}function zs(e){try{return {knowledge:ae(e),groups:ce(e)}}catch{return {knowledge:[],groups:[]}}}function Fn(e,t,n){let s=w(e)[t]??5,o=Math.max(0,Math.min(10,s+n));return F(e,t,o),o}function qs(e,t,n){return ke(e,()=>{let s=e.prepare("SELECT feedback, topic_id FROM lessons WHERE id = ?").get(t);if(!s)return null;let o=s.feedback??null,r=w(e);if(o===n)return {topic_id:s.topic_id,previous:o,delta:0,confidence:r[s.topic_id]??null};$e(e,t,n);let i=(n==="know"?1:0)-(o==="know"?1:0),a=i!==0?Fn(e,s.topic_id,i):r[s.topic_id]??null;return {topic_id:s.topic_id,previous:o,delta:i,confidence:a}})}function $n(e){try{let t=new Date,n=re(e),s=new Date(t.getTime()-24*36e5).toISOString(),o=new Date(t.getTime()-7*864e5).toISOString(),r=$(e,s),i=$(e,o),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:r,lessons_this_week:i,imported_lessons:re(e,{imported:!0}),weakest_topics:p,strongest_topics:d}}catch(t){return {error:String(t)}}}function jn(e,t){let n=Object.hasOwn(w(e),t.lesson.topic_id);for(let s of tt(t)){let o=je(e,s);if(o){if(nt(o,t))return {kind:"shared",inserted:0,duplicated:1,invalid:0,lesson:o,topic_tracked:n};continue}let r=et(t,s);if(oe(e,r))return {kind:"shared",inserted:1,duplicated:0,invalid:0,lesson:r,topic_tracked:n}}return {kind:"shared",inserted:0,duplicated:0,invalid:1,lesson:null,topic_tracked:n}}function Vs(e,t){let n=Qe(t);return n.kind==="lessons"?{kind:"lessons",...ie(e,n.records),lesson:null,topic_tracked:null}:jn(e,n.payload)}function Js(e){try{return Pe(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(!de(e).knowledge_ready)return s("onboarding not complete");let o=D(e),r="pacing disabled (nudge_every=0)";if(o.nudge_every>0){let a=Xe(e,t??"__nosession__",o.nudge_scope),{pending:l}=pe(e),p=l?Math.min(ue,o.nudge_every):o.nudge_every;if(r=`${a}/${p}${l?" (retry window)":""}`,a<p)return s(`paced (${r})`)}let i=st(e);return i.allowed?(Be(e),{cue:true,reason:`cue (${r})`,nextLessonNumber:Number($n(e).total_lessons??0)+1}):s(`rate limited: ${i.reason??"denied"}`)}function Qs(e,t){try{let n=[],s=!0;if(!de(e).knowledge_ready)return {wouldCue:!1,reasons:["onboarding not complete \u2014 no knowledge topics yet"]};let o=D(e);if(o.nudge_every>0){let i=le(e,t??"__nosession__",o.nudge_scope)+1,{pending:a}=pe(e),l=a?Math.min(ue,o.nudge_every):o.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 r=st(e);return r.allowed?n.push("rate limit: allowed"):(s=!1,n.push(`rate limited: ${r.reason??"denied"}`)),{wouldCue:s,reasons:n}}catch(n){return {wouldCue:false,reasons:[`check failed: ${n}`]}}}export{pe as $,x as A,Ws as Aa,Ae as B,st as Ba,Bt as C,zs as Ca,ke as D,Fn as Da,cs as E,qs as Ea,ls as F,$n as Fa,re as G,Vs as Ga,us as H,Js as Ha,ps as I,Zs as Ia,ds as J,Qs as Ja,ms as K,nn as L,gs as M,hs as N,je as O,fs as P,ae as Q,F as R,Es as S,Oe as T,_s as U,sn as V,Ss as W,D as X,y as Y,ys as Z,Ts as _,N as a,bs as aa,ye as b,de as ba,gt as c,Rs as ca,ht as d,rn as da,Te as e,an as ea,X as f,cn as fa,B as g,ln as ga,ft as h,Ls as ha,qn as i,un as ia,Rt as j,Ns as ja,Lt as k,Os as ka,Nt as l,xs as la,Ot as m,ws as ma,xt as n,Ds as na,wt as o,Cs as oa,O as p,ee as pa,ne as q,Is as qa,se as r,h as ra,U as s,js as sa,Ce as t,Ps as ta,M as u,qe as ua,as as v,Dn as va,Ft as w,Hs as wa,$t as x,Xs as xa,v as y,Cn as ya,Xt as z,Bs as za};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {e,g,b,d as d$1}from'./chunk-
|
|
2
|
+
import {e,g,b,d as d$1}from'./chunk-QFIAOASG.js';export{c as buildLessonCue,d as cmdLessonReady,b as cmdOnboardHook,g as cmdPromptHook,e as cmdStopHook,a as parseHookPayload}from'./chunk-QFIAOASG.js';import {a as a$1,d as d$2,e as e$1,g as g$2,b as b$2,f}from'./chunk-QU6UQJWC.js';import {c,b as b$1,a as a$2,e as e$3,f as f$1}from'./chunk-MRABMPXO.js';import {h,m,g as g$1,e as e$2,d as d$3,n,o,p,q as q$2}from'./chunk-XLFZCPHN.js';import {a,E,B as B$1,Ca,H as H$1,O,K,I as I$1,Ea,X as X$1,ta,Y,x as x$1,sa,za,Aa,xa,wa,ua,Ga,ra,Ba,Fa,w,R,V,S as S$1,T as T$1,U,W,oa,Q as Q$1,L,s,qa,q as q$1,r,z,C as C$1,y,ba,Z as Z$1,$,Ja}from'./chunk-YMXRXYRU.js';import {spawn,spawnSync,execFileSync}from'child_process';import {writeFileSync,existsSync,readFileSync,rmSync,mkdirSync,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 d=(e="")=>{console.log(e);};function _o(){let e=platform();if(e==="darwin")return join(homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let t=process.env.APPDATA??join(homedir(),"AppData","Roaming");return join(t,"Claude","claude_desktop_config.json")}let o=process.env.XDG_CONFIG_HOME??join(homedir(),".config");return join(o,"Claude","claude_desktop_config.json")}var I=join(homedir(),".claude","settings.json"),q=join(homedir(),".claude","skills","devcoach"),ke=join(homedir(),".gemini"),x=join(ke,"settings.json"),Jo=join(ke,"extensions","devcoach","gemini-extension.json"),$e=join(homedir(),".codex"),H=join($e,"hooks.json"),X=join($e,"config.toml"),N=join(homedir(),".agents","skills","devcoach");function xo(e){for(let o of (process.env.PATH??"").split(":"))if(o)try{return accessSync(join(o,e),constants.X_OK),join(o,e)}catch{}return null}var Z=e=>xo(e)!==null;function Io(){return Z("devcoach")?{command:"devcoach",args:["mcp"]}:{command:"npx",args:["-y","devcoach","mcp"]}}function ye(){let e=xo("devcoach");return e?e.includes("fnm_multishells")?"devcoach":e:"npx -y devcoach"}function vo(e,o,t){if(!Z(e))return "";t&&spawnSync(e,t,{encoding:"utf8"});let s=spawnSync(e,o,{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`${e} mcp add\``:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:`${a$1.red(`${e} mcp add failed:`)} ${(s.stderr||s.stdout||"").trim()}`}function Wo(e,o){if(!Z("claude"))return "";o&&spawnSync("claude",["mcp","remove","--scope",e,"devcoach"],{encoding:"utf8"});let t=Io(),s=spawnSync("claude",["mcp","add","--scope",e,"devcoach",t.command,"--",...t.args],{encoding:"utf8"});return s.status===0?`${a$1.green("\u2713")} Registered via \`claude mcp add\` (scope: ${e})`:`${s.stderr??""}${s.stdout??""}`.toLowerCase().includes("already")?`${a$1.yellow("Already registered")} in Claude Code (use --force to overwrite)`:`${a$1.red("claude mcp add failed:")} ${(s.stderr||s.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 ue(e,o,t){let s=C(e);if(!s.ok)return s.error;let r=s.data;r.mcpServers??={};let a=r.mcpServers;return a.devcoach&&!t?`${a$1.yellow("Already registered")} in ${e} (use --force to overwrite)`:(a.devcoach=o,mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(r,null,2)}
|
|
3
3
|
`),`${a$1.green("\u2713")} Installed into ${e}`)}function Yo(){let e=ye();return {Stop:{hooks:[{type:"command",command:`${e} stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} prompt-hook`,timeout:30}]}}}function Xo(){let e=ye();return {AfterAgent:{hooks:[{type:"command",command:`${e} gemini-stop-hook`,timeout:6e4}]},BeforeAgent:{hooks:[{type:"command",command:`${e} gemini-prompt-hook`,timeout:3e4}]}}}function qo(){let e=ye();return {Stop:{hooks:[{type:"command",command:`${e} codex-stop-hook`,timeout:60}]},UserPromptSubmit:{hooks:[{type:"command",command:`${e} codex-prompt-hook`,timeout:30}]}}}function Lo(e){return Object.entries(e.enabledPlugins??{}).filter(([o,t])=>t&&o.startsWith("devcoach@")).map(([o])=>o)}function ve(e){return Lo(e).length>0}function Q(){return existsSync(Jo)}function ee(e){return /dev-?coach/i.test(e)&&/\b(?:stop-hook|prompt-hook|onboard-hook|lesson-ready)\b/.test(e)}function we(e,o){let t=C(e);if(!t.ok)return t.error;let s=t.data;s.hooks??={};let r=s.hooks,a=false;for(let[c,l]of Object.entries(o)){r[c]??=[];let u=r[c],f=u.map((m,p)=>[m,p]).filter(([m])=>(m.hooks??[]).some(p=>ee(p.command??""))).map(([,m])=>m),[g]=f;if(!(f.length===1&&g!==void 0&&JSON.stringify(u[g])===JSON.stringify(l))){for(let m of f.toReversed())u.splice(m,1);u.push(l),a=true;}}return a?(mkdirSync(dirname(e),{recursive:true}),writeFileSync(e,`${JSON.stringify(s,null,2)}
|
|
4
4
|
`),`${a$1.green("\u2713")} Hooks installed into ${e} (${Object.keys(o).join(" + ")})`):`${a$1.yellow("Already installed")} in ${e} (current layout)`}function Zo(){let e=C(I);return e.ok?ve(e.data)?`${a$1.yellow("Skipped")} \u2014 the devcoach plugin already provides the hooks (installing both would double-count interactions)`:we(I,Yo()):e.error}function Qo(){return Q()?`${a$1.yellow("Skipped")} \u2014 the devcoach Gemini extension already provides the hooks (installing both would double-count interactions)`:we(x,Xo())}function B(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 he(e,o){if(!o&&B(e)==="current")return `${a$1.yellow("Already installed")} in ${e} (current version)`;mkdirSync(e,{recursive:true}),writeFileSync(join(e,"SKILL.md"),e$3());let t=f$1();if(t.length){let s=join(e,"references");mkdirSync(s,{recursive:true});for(let r of t)writeFileSync(join(s,r.name),r.content);}return writeFileSync(join(e,".devcoach-version"),`${a}
|
|
5
5
|
`),`${a$1.green("\u2713")} Installed into ${e}`}function en(e){return Q()?`${a$1.yellow("Skipped")} \u2014 bundled with the devcoach Gemini extension`:he(N,e)}function T(e,o){let t=C(e);return t.ok&&(t.data.hooks?.[o]??[]).some(s=>(s.hooks??[]).some(r=>ee(r.command??"")))}function Ce(){let e=[{name:"Claude Code",dir:q,cmd:"devcoach install",hooked:()=>T(I,"Stop")},{name:"Gemini/Codex",dir:N,cmd:"devcoach install --gemini (or --codex)",hooked:()=>T(x,"AfterAgent")||T(H,"Stop")}];for(let o of e){let t=B(o.dir);if(t==="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(t==="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 Do(e){let o=e.claudeCode||e.claudeDesktop||e.gemini||e.codex,t=e.claudeCode||!o,s=e.claudeDesktop||!o,r=Io(),a=false;if(d(a$1.bold("Setting up devcoach")+a$1.dim(` (${r.command} ${r.args.join(" ")})`)),d(),t){d(a$1.bold("Claude Code"));let c=Wo("user",e.force);if(!c){let l=join(homedir(),".claude.json");c=ue(l,{type:"stdio",env:{},...r},e.force),a=true;}d(` MCP server\u2026 ${c}`),e.skipHook||d(` Hooks\u2026 ${Zo()}`),d(` Skill\u2026 ${he(q,e.force)}`),d();}if(s&&(d(a$1.bold("Claude Desktop")),d(` MCP server\u2026 ${ue(_o(),r,e.force)}`),a=true,d()),e.gemini){d(a$1.bold("Gemini CLI (beta)"));let c=vo("gemini",["mcp","add","-s","user","devcoach",r.command,...r.args],e.force?["mcp","remove","-s","user","devcoach"]:null);c||(c=ue(x,r,e.force)),d(` MCP server\u2026 ${c}`),e.skipHook||d(` Hooks\u2026 ${Qo()}`),d(` Skill\u2026 ${en(e.force)}`),d();}if(e.codex){d(a$1.bold("Codex CLI (beta)"));let c=vo("codex",["mcp","add","devcoach","--",r.command,...r.args],e.force?["mcp","remove","devcoach"]:null);c||(c=`${a$1.yellow("Manual step")} \u2014 codex CLI not found. Add to ${X}:
|
|
@@ -14,23 +14,23 @@ Codex CLI wiring (beta)`));let o=C(H);if(!o.ok){e.bad(`${H} is not valid JSON \u
|
|
|
14
14
|
${a$1.bold("devcoach doctor")} ${a$1.dim(`v${a}`)}
|
|
15
15
|
`),d(a$1.bold("Environment")),Number.parseInt(process.versions.node.split(".")[0]??"0",10)>=24?o(`Node ${process.versions.node} (\u2265 24)`):s(`Node ${process.versions.node} \u2014 devcoach needs Node \u2265 24 (embedded node:sqlite)`),d(a$1.bold(`
|
|
16
16
|
Claude Code wiring`));let a$2=C(I);if(!a$2.ok)s(`${I} is not valid JSON \u2014 hooks cannot run`);else {let c=ve(a$2.data),l=Se(a$2.data),u=Lo(a$2.data);if(u.length>1&&s(`${u.length} devcoach plugins enabled (${u.join(", ")}) \u2014 their hooks fire twice and interactions are double-counted. Keep one enabled (/plugin).`),c&&l.length)s("devcoach hooks are registered TWICE (plugin + settings.json) \u2014 interactions are double-counted. Disable the plugin or remove the settings.json entries.");else if(c)o("hooks provided by the devcoach plugin");else if(!l.length)s(`no devcoach hooks in ${I} \u2014 run ${a$1.bold("devcoach install")}`);else {let m=l.filter(({cmd:k})=>k.command.includes("onboard-hook")||k.command.includes("lesson-ready")),p=l.filter(k=>k.event==="Stop").length;m.length?t(`legacy two-entry Stop layout \u2014 run ${a$1.bold("devcoach install")} to merge into one stop-hook entry (fewer spawns per stop)`):p>1?t(`${p} devcoach Stop entries \u2014 interactions are counted ${p}\xD7 per stop; run ${a$1.bold("devcoach install")} to keep only the current one`):o(`Stop hook wired (${p} entry)`),l.some(k=>k.event==="UserPromptSubmit")?o("UserPromptSubmit priming hook wired"):t(`no UserPromptSubmit priming hook \u2014 run ${a$1.bold("devcoach install")} to add it (lessons land more reliably)`),_e(e,l,"devcoach install");}let f=B(q);f==="current"?o("Claude Code skill installed (current version)"):c?o("Claude Code skill bundled with the plugin"):t(f==="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 g=C(join(homedir(),".claude.json"));g.ok&&g.data.mcpServers?.devcoach?o("MCP server registered (user scope)"):t(`MCP server not found in ~/.claude.json \u2014 it may be registered elsewhere (check with ${a$1.bold("claude mcp get devcoach")})`);}if(existsSync(ke)&&on(e),existsSync($e)&&nn(e),d(a$1.bold(`
|
|
17
|
-
Database & pacing`)),!existsSync(
|
|
18
|
-
Verdict`));let k=
|
|
17
|
+
Database & pacing`)),!existsSync(r)){t(`no database yet (${r}) \u2014 onboarding runs on the first technical task`),d();return}try{E(c=>{o(`database opens (${r})`);let l=B$1(c);if(l)s(z(l)),d(" 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 $=C$1(c);o(`schema v${y}`+($.upgradedBy?`, last upgraded by devcoach ${$.upgradedBy}`:""));}ba(c).knowledge_ready?o("onboarding complete"):t("onboarding not complete \u2014 the next stop cues it");let u=X$1(c);d(` settings: max_per_day=${u.max_per_day} \xB7 min_gap_minutes=${u.min_gap_minutes} \xB7 nudge_every=${u.nudge_every} \xB7 nudge_scope=${u.nudge_scope}`);let f=Z$1(c),g=f.reduce(($,A)=>$+A.interactions,0);d(` pacing: ${f.length} session(s) counted, ${g} interaction(s) total`+(f[0]?` \u2014 latest ${f[0].session_id.slice(0,8)}\u2026 at ${f[0].interactions}`:""));let m=$(c);m.pending?t(`a cue is pending since ${m.last_cue_at} (retry threshold armed)`):m.last_skip_reason&&d(` last skip: "${m.last_skip_reason}"`);let p=Ba(c);p.allowed?o("rate limit: allowed"):t(`rate limit: ${p.reason}`),d(a$1.bold(`
|
|
18
|
+
Verdict`));let k=Ja(c,f[0]?.session_id??null);k.wouldCue?o("the next eligible stop WOULD cue a lesson"):t("the next eligible stop would NOT cue a lesson:");for(let $ of k.reasons)d(` \xB7 ${$}`);});}catch(c){s(`database check failed: ${c}`);}d();}function pe(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 t=o.data.mcpServers;return t?.devcoach?(delete t.devcoach,writeFileSync(e,`${JSON.stringify(o.data,null,2)}
|
|
19
19
|
`),`${a$1.green("\u2713")} Removed from ${e}`):`${a$1.yellow("Not registered")} in ${e}`}function ge(e,o){if(!Z(e))return "";let t=spawnSync(e,o,{encoding:"utf8"}),s=`${t.stderr??""}${t.stdout??""}`.toLowerCase();return t.status===0?`${a$1.green("\u2713")} Removed via \`${e} mcp remove\``:/not found|no .*server|does not exist|no such/.test(s)?`${a$1.yellow("Not registered")} in ${e}`:`${a$1.red(`${e} mcp remove failed:`)} ${(t.stderr||t.stdout||"").trim()}`}function fe(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 t=o.data.hooks??{},s=[];for(let[r,a]of Object.entries(t)){if(!a)continue;let c=a.filter(l=>!(l.hooks??[]).some(u=>ee(u.command??"")));c.length!==a.length&&(s.push(r),c.length?t[r]=c:delete t[r]);}return s.length===0?`${a$1.yellow("No devcoach hooks")} in ${e}`:(Object.keys(t).length===0&&delete o.data.hooks,writeFileSync(e,`${JSON.stringify(o.data,null,2)}
|
|
20
|
-
`),`${a$1.green("\u2713")} Hooks removed from ${e} (${s.join(" + ")})`)}function wo(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 tn(e){if(e)return true;if(!process.stdin.isTTY)return d(`${a$1.yellow("\u2192")} Not a terminal \u2014 pass --yes to delete ${
|
|
20
|
+
`),`${a$1.green("\u2713")} Hooks removed from ${e} (${s.join(" + ")})`)}function wo(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 tn(e){if(e)return true;if(!process.stdin.isTTY)return d(`${a$1.yellow("\u2192")} Not a terminal \u2014 pass --yes to delete ${q$1} without a prompt.`),false;let o=createInterface({input:process.stdin,output:process.stdout});try{let t=await o.question(`Delete ${q$1} \u2014 every lesson, your profile and the notebook? [y/N] `);return /^y(es)?$/i.test(t.trim())}finally{o.close();}}async function Ho(e){let o=e.claudeCode||e.claudeDesktop||e.gemini||e.codex,t=e.all||e.claudeCode||!o,s=e.all||e.claudeDesktop||!o,r=e.all||e.gemini,a=e.all||e.codex;if(d(a$1.bold("Removing devcoach from your agents")),d(),t){d(a$1.bold("Claude Code"));let c=C(I);c.ok&&ve(c.data)&&d(` ${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 l=ge("claude",["mcp","remove","--scope","user","devcoach"]);l||(l=pe(join(homedir(),".claude.json"))),d(` MCP server\u2026 ${l}`),d(` Hooks\u2026 ${fe(I)}`),d(` Skill\u2026 ${wo(q)}`),d();}if(s&&(d(a$1.bold("Claude Desktop")),d(` MCP server\u2026 ${pe(_o())}`),d(` ${a$1.yellow("\u2192")} Restart Claude Desktop to drop the server.`),d()),r){d(a$1.bold("Gemini CLI (beta)"));let c=ge("gemini",["mcp","remove","-s","user","devcoach"]);c||(c=pe(x)),d(` MCP server\u2026 ${c}`),d(` Hooks\u2026 ${fe(x)}`),Q()&&d(` ${a$1.yellow("\u2192")} The devcoach Gemini extension is installed \u2014 remove it with: gemini extensions uninstall devcoach`),d();}if(a){d(a$1.bold("Codex CLI (beta)"));let c=ge("codex",["mcp","remove","devcoach"]);c||(c=`${a$1.yellow("Manual step")} \u2014 codex CLI not found. Delete the [mcp_servers.devcoach] table from ${X}`),d(` MCP server\u2026 ${c}`),d(` Hooks\u2026 ${fe(H)}`),d();}if(r||a){let c=!r&&T(x,"AfterAgent")||!a&&T(H,"Stop");d(a$1.bold("Shared skill (Gemini + Codex)")),d(` Skill\u2026 ${c?`${a$1.yellow("Kept")} \u2014 still used by the other client (${N})`:wo(N)}`),d();}e.data?(d(a$1.bold("Coaching data")),existsSync(q$1)?await tn(e.yes)?(rmSync(q$1,{recursive:true,force:true}),d(` ${a$1.green("\u2713")} Deleted ${q$1}`)):d(` ${a$1.yellow("Kept")} ${q$1}`):d(` ${a$1.yellow("Nothing to delete")} (${q$1} does not exist)`),d()):d(a$1.dim(`Your lessons, profile and notebook are still in ${q$1} \u2014 keep them for a
|
|
21
21
|
reinstall, or delete them with: devcoach uninstall --data (devcoach backup first).`)),d(a$1.dim(`Then remove the program itself: brew uninstall devcoach \xB7 npm uninstall -g devcoach
|
|
22
|
-
(npx: nothing to remove).`));}function rn(e,o){return e==="darwin"?["open",[o]]:e==="win32"?["cmd",["/c","start","",o]]:["xdg-open",[o]]}function xe(e,o=process.platform){let[t,s]=rn(o,e);try{let r=spawn(t,s,{detached:!0,stdio:"ignore"});r.on("error",()=>{}),r.unref();}catch{}}var i=(e="")=>{console.log(e);},Mo=e=>e==="junior"?a$1.green(e):e==="mid"?a$1.yellow(e):e==="senior"?a$1.red(e):e,mn=e=>e==="know"?` ${a$1.green("\u2713")}`:e==="understood"?` ${a$1.cyan("\u{1F4A1}")}`:e==="dont_know"?` ${a$1.red("\u2717")}`:"",Ro=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"),No=["know","understood","dont_know","clear"];function jo(e,o){let t=new Map;for(let r of e.groups)for(let a of r.topics)t.set(a,r.name);let s=[...e.knowledge].sort((r,a)=>a.confidence-r.confidence).map(r=>{let a=g$2(r.confidence);return [r.topic,t.get(r.topic)??"Other",b$2(a,`${r.confidence}/10`),b$2(a,f(r.confidence))]});return d$2(o,[{header:"Topic"},{header:"Group"},{header:"Confidence",justify:"right"},{header:"Bar"}],s)}function Le(e){e&&i(a$1.yellow(`\u2192 ${
|
|
23
|
-
${u}`),Le(s);}function vn(e,o){let t
|
|
24
|
-
${a$1.bold("Step 1")} \u2014 Restore from backup`);let s=await o("Path to existing backup zip (Enter to skip)","");if(s){existsSync(s)||(i(a$1.red(`File not found: ${s}`)),process.exit(1));let g=
|
|
25
|
-
${a$1.bold("Step 2")} \u2014 Build your knowledge profile`);let r=(await o("Mode: [a]utomatic (detect from files) / [m]anual (type your stack)","a")).toLowerCase(),a={};if(r.startsWith("a")){let m=o
|
|
22
|
+
(npx: nothing to remove).`));}function rn(e,o){return e==="darwin"?["open",[o]]:e==="win32"?["cmd",["/c","start","",o]]:["xdg-open",[o]]}function xe(e,o=process.platform){let[t,s]=rn(o,e);try{let r=spawn(t,s,{detached:!0,stdio:"ignore"});r.on("error",()=>{}),r.unref();}catch{}}var i=(e="")=>{console.log(e);},Mo=e=>e==="junior"?a$1.green(e):e==="mid"?a$1.yellow(e):e==="senior"?a$1.red(e):e,mn=e=>e==="know"?` ${a$1.green("\u2713")}`:e==="understood"?` ${a$1.cyan("\u{1F4A1}")}`:e==="dont_know"?` ${a$1.red("\u2717")}`:"",Ro=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"),No=["know","understood","dont_know","clear"];function jo(e,o){let t=new Map;for(let r of e.groups)for(let a of r.topics)t.set(a,r.name);let s=[...e.knowledge].sort((r,a)=>a.confidence-r.confidence).map(r=>{let a=g$2(r.confidence);return [r.topic,t.get(r.topic)??"Other",b$2(a,`${r.confidence}/10`),b$2(a,f(r.confidence))]});return d$2(o,[{header:"Topic"},{header:"Group"},{header:"Confidence",justify:"right"},{header:"Bar"}],s)}function Le(e){e&&i(a$1.yellow(`\u2192 ${z(e)}`));}function un(){let{profile:e,compat:o}=E(t=>({profile:Ca(t),compat:B$1(t)}));i(jo(e,"Knowledge Map")),Le(o);}function pn(e){let o=null,t=E(l=>(o=B$1(l),H$1(l,{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(t.length===0){i(a$1.dim("No lessons found."));return}let s=t.some(l=>l.project||l.branch||l.commit_hash),r=[{header:""},{header:"Date"},{header:"Topic"},{header:"Title"},{header:"Level",justify:"center"},{header:"Categories"}];s&&r.push({header:"Project"},{header:"Branch"},{header:"Commit"});let a=t.some(l=>l.imported);a&&r.push({header:"Shared by"});let c=t.map(l=>{let u=[l.starred?a$1.yellow("\u2605"):a$1.dim("\xB7"),l.timestamp.slice(0,10),a$1.cyan(l.topic_id),l.title+mn(l.feedback),Mo(l.level),l.categories.join(", ")];return s&&u.push(l.project??"",l.branch?a$1.magenta(l.branch):"",l.commit_hash?a$1.cyan(l.commit_hash.slice(0,7)):""),a&&u.push(l.imported?l.shared_by??a$1.dim("anonymous"):""),u});i(d$2("Lessons",r,c)),Le(o);}function gn(e){let o=E(s=>O(s,e));o===null&&(i(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),i(e$1(a$1.bold(o.title))),i(`${a$1.dim("ID:")} ${o.id}`),i(`${a$1.dim("Date:")} ${o.timestamp.slice(0,19).replace("T"," ")}`),i(`${a$1.dim("Topic:")} ${o.topic_id}`),i(`${a$1.dim("Categories:")} ${o.categories.join(", ")}`),i(`${a$1.dim("Level:")} ${Mo(o.level)}`);let t=o.starred?a$1.yellow("\u2605 starred"):a$1.dim("\u2606 not starred");if(i(`${a$1.dim("Star:")} ${t} ${a$1.dim("Feedback:")} ${Ro(o.feedback)}`),o.imported&&i(`${a$1.dim("Shared by:")} ${o.shared_by??a$1.dim("anonymous")}`),o.task_context&&i(`${a$1.dim("Context:")} ${o.task_context}`),o.project||o.repository||o.branch||o.commit_hash||o.folder){let s=[];o.project&&s.push(`project=${o.project}`),o.repository&&s.push(`repo=${o.repository}`),o.branch&&s.push(`branch=${a$1.magenta(o.branch)}`),o.commit_hash&&s.push(`commit=${a$1.cyan(o.commit_hash.slice(0,7))}`),o.folder&&s.push(`folder=${o.folder}`),i(`${a$1.dim("Git:")} ${s.join(" \xB7 ")}`);}i(e$1()),i(o.summary);}function Ao(e,o){E(s=>O(s,e)===null?false:(K(s,e,o),true))||(i(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),i(o?`Lesson ${a$1.cyan(e)} \u2192 ${a$1.yellow("\u2605 starred")}`:`Lesson ${a$1.cyan(e)} \u2192 ${a$1.dim("\u2606 unstarred")}`);}function fn(e){E(t=>I$1(t,e))||(i(a$1.red(`Lesson '${e}' not found.`)),process.exit(1)),i(`Lesson ${a$1.cyan(e)} deleted.`);}var Po=e=>e==="completed"?a$1.green(e):e==="abandoned"?a$1.dim(e):a$1.cyan(e);function hn(){let e=E(s=>h(s));if(e.length===0){i(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"}],t=e.map(s=>{let r=m(s);return [a$1.cyan(s.id),s.title,`${r.done}/${r.total}`,Po(s.status),s.updated_at.slice(0,10)]});i(d$2("Courses",o,t));}function bn(e){let o=E(r=>g$1(r,e));o===null&&(i(a$1.red(`Course '${e}' not found.`)),process.exit(1));let t=m(o);i(e$1(a$1.bold(o.title))),i(`${a$1.dim("ID:")} ${o.id}`),i(`${a$1.dim("Topic:")} ${a$1.cyan(o.topic_id)} ${a$1.dim("Status:")} ${Po(o.status)} ${a$1.dim("Progress:")} ${t.done}/${t.total}`),o.goal&&i(`${a$1.dim("Goal:")} ${o.goal}`),o.lesson_id&&i(`${a$1.dim("From:")} lesson ${a$1.cyan(o.lesson_id)}`);let s=e$2(o.id);if(i(`${a$1.dim("Document:")} ${s??a$1.dim(`not written yet \u2192 ${d$3(o.id)}`)}`),o.prerequisites.length&&i(`${a$1.dim("Chain:")} ${o.prerequisites.map(r=>r.known?a$1.green(`\u2713 ${r.concept}`):a$1.red(`\u2717 ${r.concept}`)).join(a$1.dim(" \u2192 "))}`),i(""),o.steps.length===0){i(a$1.dim("No steps registered yet."));return}for(let r of o.steps){let a=r.status==="done"?a$1.green("\u25CF"):r.status==="skipped"?a$1.dim("\u25CC"):a$1.cyan("\u25CB");i(` ${a} ${r.position}. ${r.title} ${a$1.dim(`${r.kind} \xB7 #${r.anchor}${r.status!=="todo"?` \xB7 ${r.status}`:""}`)}`);}}function kn(e,o){No.includes(o)||(i(a$1.red(`Invalid feedback '${o}'. Use: ${No.join(" | ")}`)),process.exit(1));let t=o==="clear"?null:o,s=E(a=>Ea(a,e,t));s===null&&(i(a$1.red(`Lesson '${e}' not found.`)),process.exit(1));let r;if(t===null)r="feedback cleared";else if(s.delta!==0&&s.confidence!==null){let a=s.confidence-s.delta;r=`${a$1.cyan(s.topic_id)} confidence: ${a} \u2192 ${a$1.bold(String(s.confidence))}`;}else r=`${a$1.cyan(s.topic_id)} confidence unchanged`;i(`Lesson ${a$1.cyan(e)} \u2192 ${Ro(t)} (${r})`);}function $n(){let e=E(r=>X$1(r)),o=Math.floor(e.min_gap_minutes/60),t=e.min_gap_minutes%60,s=o?`${o}h ${t}m`:`${t}m`;i(d$2("Settings",[{header:"Key"},{header:"Value",justify:"right"}],[["max_per_day",String(e.max_per_day)],["min_gap_minutes",`${e.min_gap_minutes} (${s})`],["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 yn(){let{stats:e,rateLimit:o,settings:t,compat:s}=E(f=>({stats:Fa(f),rateLimit:Ba(f),settings:X$1(f),compat:B$1(f)})),r=f=>Number(e[f]??0),a=o.allowed?a$1.green("Available now"):a$1.yellow(o.reason??"");i(d$2("Coaching Stats",[{header:"Metric"},{header:"Value",justify:"right"}],[["Total lessons",String(r("total_lessons"))],["Lessons today (24h)",`${r("lessons_today")} / ${t.max_per_day}`],["Lessons this week",String(r("lessons_this_week"))],["Next lesson",a]],false));let c=e.weakest_topics??[],l=e.strongest_topics??[];if(c.length||l.length){let f=[];for(let g=0;g<Math.max(c.length,l.length);g++){let m=c[g],p=l[g];f.push([m?`${a$1.red(m.topic)} ${a$1.dim(`(${m.confidence})`)}`:"",p?`${a$1.green(p.topic)} ${a$1.dim(`(${p.confidence})`)}`:""]);}i(d$2(void 0,[{header:"Weakest topics"},{header:"Strongest topics"}],f));}let u=Ce();u&&i(`
|
|
23
|
+
${u}`),Le(s);}function vn(e,o){let t=["max_per_day","min_gap_minutes","nudge_every","nudge_scope","share_name"];if(t.includes(e)||(i(a$1.red(`Unknown key '${e}'. Valid keys: ${t.join(", ")}`)),process.exit(1)),e==="nudge_scope"&&o!=="session"&&o!=="global"&&(i(a$1.red(`Invalid nudge_scope '${o}'. Use: session | global`)),process.exit(1)),e==="share_name"){o.trim().length>w&&(i(a$1.red(`share_name must be at most ${w} characters.`)),process.exit(1));let s=x$1(o);E(r=>Y(r,e,s)),i(s?a$1.green(`Set share_name = ${s}`):a$1.green("Cleared share_name"));return}if(e==="nudge_every"){let s=Number.parseInt(o,10);(!Number.isInteger(s)||s<0)&&(i(a$1.red(`Invalid nudge_every '${o}'. Use a non-negative integer (0 = off).`)),process.exit(1));}E(s=>Y(s,e,o)),i(a$1.green(`Set ${e} = ${o}`));}function wn(e,o,t){let s=e.trim();s||(i(a$1.red("Topic name must not be empty.")),process.exit(1)),E(a=>{R(a,s,o),t&&t!=="Other"&&V(a,s,t);});let r=t&&t!=="Other"?` \u2192 ${a$1.cyan(t)}`:"";i(`${a$1.green("Added")} ${a$1.bold(s)} (confidence ${a$1.cyan(String(o))})${r}`);}function Cn(e){let o=E(t=>S$1(t,e));i(o?`${a$1.green("Removed")} ${a$1.bold(e)} from knowledge map`:a$1.yellow(`Topic '${e}' not found.`));}function Sn(e){let o=e.trim();(!o||o==="Other")&&(i(a$1.red("Invalid group name.")),process.exit(1)),E(t=>T$1(t,o)),i(`${a$1.green(`Group '${a$1.cyan(o)}' ready.`)} Assign topics with: devcoach group-assign <topic> "${o}"`);}function _n(e){let o=E(t=>U(t,e));i(o?`${a$1.green(`Removed group '${a$1.cyan(e)}'`)}`:a$1.yellow(`Group '${e}' not found.`));}function xn(e,o){E(s=>s.prepare("SELECT topic FROM knowledge WHERE topic = ?").get(e)===void 0?false:(o==="Other"?W(s,e):V(s,e,o),true))||(i(a$1.red(`Topic '${e}' not in knowledge map. Add it first.`)),process.exit(1)),i(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 In(e){let{lessonsCount:o,knowledgeCount:t,data:s$1}=E(a=>({lessonsCount:L(a).length,knowledgeCount:Q$1(a).length,data:oa(a)}));writeFileSync(e,s$1);let r=existsSync(s)?" + notebook":"";i(`${a$1.green("Backup saved:")} ${e} (${a$1.cyan(String(o))} lessons, ${a$1.cyan(String(t))} topics${r})`);}function Ln(e){existsSync(e)||(i(a$1.red(`File not found: ${e}`)),process.exit(1));let o=E(s=>qa(s,readFileSync(e)));o.settings&&i(`${a$1.green("\u2713")} Settings restored`),o.topics&&i(`${a$1.green("\u2713")} Knowledge map restored (${a$1.cyan(String(o.topics))} topics)`);let t=[`${a$1.cyan(String(o.lessons))} imported`];o.skipped&&t.push(`${a$1.yellow(String(o.skipped))} duplicates skipped`),o.invalid&&t.push(`${a$1.red(String(o.invalid))} rejected (invalid)`),i(`${a$1.green("\u2713")} Lessons: ${t.join(", ")}`),o.learning_state&&i(`${a$1.green("\u2713")} Notebook restored`);}var oe=e=>{console.error(a$1.dim(e));};function Dn(e,o){o.by!==void 0&&o.anonymous&&(i(a$1.red("Use either --by <name> or --anonymous, not both.")),process.exit(1)),!e&&!o.last&&(i(a$1.red("Give a lesson id, or --last for the most recent one.")),process.exit(2));let t=E(a=>{let c=o.last?H$1(a,{imported:false,page:1,per_page:1})[0]??null:O(a,e);if(!c)return null;let l=X$1(a),u=ta({explicit:o.by??null,anonymous:o.anonymous,setting:l.share_name,gitUserName:n()}),f=false;return o.by?.trim()&&!l.share_name&&(Y(a,"share_name",x$1(o.by)),f=true),{payload:sa(c,{includeContext:!!o.withContext,sharedBy:u}),remembered:f}});t||(i(a$1.red(o.last?"No lessons to share yet.":`Lesson '${e}' not found.`)),process.exit(1));let{payload:s,remembered:r}=t;if(o.file!==void 0&&o.file!==false){let a=typeof o.file=="string"?o.file:za(s);writeFileSync(a,Aa(s)),i(`${a$1.green("Saved:")} ${a}`);}else o.link?i(xa(s)):(i(wa(s)),ua(s).length>16e3&&oe("Long lesson \u2014 prefer --file for a friendlier hand-off."));r&&oe("(remembered as share_name \u2014 change it with: devcoach set share_name <name>)"),s.shared_by===null&&oe("Shared anonymously (set a name with: devcoach set share_name <name>)");}function On(){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,t]of e)try{let s=execFileSync(o,t,{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]});if(s.trim())return s}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 Hn(e){return e===void 0?On():e==="-"?text(process.stdin):existsSync(e)?readFileSync(e,"utf8"):p(e)&&!e.includes("#devcoach:lesson:")?q$2(e):e}async function Nn(e){try{let o=await Hn(e),t=E(a=>Ga(a,o));if(t.kind==="lessons"){let a=[`${a$1.cyan(String(t.inserted))} imported`];t.duplicated&&a.push(`${a$1.yellow(String(t.duplicated))} duplicates skipped`),t.invalid&&a.push(`${a$1.red(String(t.invalid))} rejected (invalid)`),i(`${a$1.green("\u2713")} Lessons: ${a.join(", ")}`);return}let s=t.lesson;s||(i(a$1.red("Could not store the lesson.")),process.exit(1));let r=s.shared_by?`shared by ${s.shared_by}`:"shared anonymously";t.duplicated?i(`${a$1.yellow("Already in your log")} as ${a$1.cyan(s.id)} (${r})`):i(`${a$1.green("\u2713 Imported")} ${a$1.bold(`"${s.title}"`)} (${r}) as ${a$1.cyan(s.id)} \u2014 topic ${a$1.cyan(s.topic_id)}`),t.topic_tracked===!1&&oe(`Topic '${s.topic_id}' is not in your knowledge map \u2014 track it with: devcoach knowledge-add ${s.topic_id}`);}catch(o){if(!(o instanceof ra))throw o;i(a$1.red(o.message)),process.exit(1);}}async function An(){let e=createInterface({input:process.stdin,output:process.stdout}),o$1=async(s,r="")=>{let a=r?` [${r}]`:"";return (await e.question(`${s}${a}: `)).trim()||r},t=async(s,r,a,c)=>{for(;;){let l=await o$1(s,String(r)),u=Number.parseInt(l,10);if(!Number.isNaN(u)&&u>=a&&u<=c)return u;i(a$1.red(`Must be ${a}\u2013${c}.`));}};try{i(e$1(a$1.bold("devcoach setup"))),i(`
|
|
24
|
+
${a$1.bold("Step 1")} \u2014 Restore from backup`);let s=await o$1("Path to existing backup zip (Enter to skip)","");if(s){existsSync(s)||(i(a$1.red(`File not found: ${s}`)),process.exit(1));let g=E(m=>qa(m,readFileSync(s)));i(`${a$1.green("\u2713")} Restored: ${g.topics} topics, ${g.lessons} lessons${g.learning_state?", notebook":""}`),i(a$1.green("Setup complete!"));return}i(`
|
|
25
|
+
${a$1.bold("Step 2")} \u2014 Build your knowledge profile`);let r=(await o$1("Mode: [a]utomatic (detect from files) / [m]anual (type your stack)","a")).toLowerCase(),a={};if(r.startsWith("a")){let m=o().folder??process.cwd(),p=c(),k=b$1(a$2(m),p.detected_stack),$=Object.keys(k).sort((v,E)=>v.localeCompare(E));if(p.scanned_projects>0){i(`
|
|
26
26
|
${a$1.dim(`Scanned ${p.scanned_projects} Claude Code project(s) from your history:`)}`);for(let v of p.projects.slice(0,8))i(a$1.dim(` ${v.name} \u2014 ${v.topics.join(", ")||"no stack signals"}`));}if($.length){i(`
|
|
27
|
-
${a$1.dim(`Detected (history + ${a$1.cyan(m)}):`)}`);for(let v of $){let E=k[v]??5,De=await o(` ${a$1.cyan(v)} (Enter=keep, 0-10=override, s=skip)`,String(E));if(De.toLowerCase()==="s")continue;let Oe=Number.parseInt(De,10);a[v]=Number.isNaN(Oe)?E:Math.max(0,Math.min(10,Oe));}}else i(a$1.dim("No technology files detected in current directory."));let A=await o(`
|
|
27
|
+
${a$1.dim(`Detected (history + ${a$1.cyan(m)}):`)}`);for(let v of $){let E=k[v]??5,De=await o$1(` ${a$1.cyan(v)} (Enter=keep, 0-10=override, s=skip)`,String(E));if(De.toLowerCase()==="s")continue;let Oe=Number.parseInt(De,10);a[v]=Number.isNaN(Oe)?E:Math.max(0,Math.min(10,Oe));}}else i(a$1.dim("No technology files detected in current directory."));let A=await o$1(`
|
|
28
28
|
Add additional topics (comma-separated, or Enter to skip)`,"");for(let v of A.split(",").map(E=>E.trim()).filter(Boolean))a[v]=await t(` Confidence for ${a$1.cyan(v)}`,5,0,10);}else for(i(a$1.dim(`
|
|
29
|
-
Enter topics: 'topic_id confidence' (e.g. python 7). Blank line when done.`));;){let g=(await o("Topic (Enter when done)","")).trim();if(!g)break;let m=g.split(/\s+/),p=m[0]??"",k=Number.parseInt(m[1]??"",10),$=Number.isNaN(k)?5:Math.max(0,Math.min(10,k));a[p]=$,i(` ${a$1.green("+")} ${a$1.cyan(p)} \u2192 ${$}`);}let c$1={};if(Object.keys(a).length&&(i(`
|
|
30
|
-
${a$1.bold("Step 3")} \u2014 Organise into groups`),(await o("Organise topics into groups? [y/N]","n")).toLowerCase().startsWith("y"))){let m=[];for(let p of Object.keys(a).sort((k,$)=>k.localeCompare($))){let k=m.length?m.join(", "):"(none yet)",$=await o(` Group for ${a$1.cyan(p)} existing: ${a$1.dim(k)} (Enter=Other)`,"");if($&&$!=="Other"){let A=c$1[$]??[];A.push(p),c$1[$]=A,m.includes($)||m.push($);}}}i(`
|
|
31
|
-
${a$1.bold("Step 4")} \u2014 Rate-limit settings`);let l=await t("Max lessons per day",2,1,20),u=await t("Min gap between lessons (minutes)",240,0,1440),f=
|
|
29
|
+
Enter topics: 'topic_id confidence' (e.g. python 7). Blank line when done.`));;){let g=(await o$1("Topic (Enter when done)","")).trim();if(!g)break;let m=g.split(/\s+/),p=m[0]??"",k=Number.parseInt(m[1]??"",10),$=Number.isNaN(k)?5:Math.max(0,Math.min(10,k));a[p]=$,i(` ${a$1.green("+")} ${a$1.cyan(p)} \u2192 ${$}`);}let c$1={};if(Object.keys(a).length&&(i(`
|
|
30
|
+
${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 p of Object.keys(a).sort((k,$)=>k.localeCompare($))){let k=m.length?m.join(", "):"(none yet)",$=await o$1(` Group for ${a$1.cyan(p)} existing: ${a$1.dim(k)} (Enter=Other)`,"");if($&&$!=="Other"){let A=c$1[$]??[];A.push(p),c$1[$]=A,m.includes($)||m.push($);}}}i(`
|
|
31
|
+
${a$1.bold("Step 4")} \u2014 Rate-limit settings`);let l=await t("Max lessons per day",2,1,20),u=await t("Min gap between lessons (minutes)",240,0,1440),f=E(g=>{g.exec("DELETE FROM knowledge; DELETE FROM knowledge_groups; DELETE FROM knowledge_group_names;");for(let[m,p]of Object.entries(a))R(g,m,p);for(let[m,p]of Object.entries(c$1))for(let k of p)V(g,k,m);return Y(g,"max_per_day",String(l)),Y(g,"min_gap_minutes",String(u)),Ca(g)});i(jo(f,"Knowledge Profile")),i(`
|
|
32
32
|
${a$1.green("Setup complete!")} ${Object.keys(a).length} topics saved.`);}finally{e.close();}}function En(){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"]];i(`
|
|
33
33
|
${a$1.bold(`devcoach ${a$1.dim(`v${a}`)}`)}`);for(let[t,s]of e)i(` ${a$1.cyan(`devcoach ${t}`.padEnd(32))} ${a$1.dim(s)}`);i(`
|
|
34
34
|
${a$1.dim("Run")} ${a$1.bold("devcoach <command> --help")} ${a$1.dim("for per-command options.")}
|
|
35
35
|
`);let o=Ce();o&&i(`${o}
|
|
36
|
-
`);}function S(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(()=>En()),e$1.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-
|
|
36
|
+
`);}function S(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(()=>En()),e$1.command("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{runStdio:o}=await import('./server-UN3IURFP.js');await o();}),e$1.command("profile").description("Show the knowledge map").action(un),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=>pn({period:o.period??"all",category:S(o.category),project:S(o.project),repository:S(o.repository),branch:S(o.branch),commit:S(o.commit),starred:!!o.starred,imported:!!o.imported,sharedBy:S(o.from),feedback:S(o.feedback),level:S(o.level),dateFrom:S(o.dateFrom),dateTo:S(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=>gn(o)),e$1.command("courses").description("List your step-by-step courses").action(()=>hn()),e$1.command("course").description("Show a course: steps, progress, document path").argument("<id>","Course ID").action(o=>bn(o)),e$1.command("star").description("Mark a lesson as starred").argument("<id>","Lesson ID").action(o=>Ao(o,true)),e$1.command("unstar").description("Remove the starred mark from a lesson").argument("<id>","Lesson ID").action(o=>Ao(o,false)),e$1.command("delete").description("Permanently delete a lesson").argument("<id>","Lesson ID").action(o=>fn(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,t)=>kn(o,t)),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,t)=>Dn(o,t)),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=>Nn(o)),e$1.command("settings").description("Show current settings").action($n),e$1.command("stats").description("Coaching statistics and rate-limit status").action(yn),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,t)=>vn(o,t)),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,t)=>wn(o,Math.max(0,Math.min(10,Number.parseInt(t.confidence??"5",10)||5)),S(t.group))),e$1.command("knowledge-remove").description("Remove a topic from the knowledge map").argument("<topic>","Topic ID").action(o=>Cn(o)),e$1.command("group-add").description("Register a new knowledge group").argument("<name>","Group name").action(o=>Sn(o)),e$1.command("group-remove").description("Delete a knowledge group (topics move to Other)").argument("<name>","Group name").action(o=>_n(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,t)=>xn(o,t)),e$1.command("backup").description("Export a full backup (settings + knowledge + lessons) as zip").argument("[file]","Output zip path","devcoach-backup.zip").action(o=>In(o)),e$1.command("restore").description("Restore from a backup zip file").argument("<file>","Path to backup zip").action(o=>Ln(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=>Do({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=>Ho({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(Oo),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 t=Number.parseInt(o.port??"7860",10)||7860,{startUi:s,stopUi:r}=await import('./app-L3Y3NV4O.js');if(o.stop){i(await r(t)?a$1.green(`\u2713 devcoach UI on port ${t} stopped`):a$1.yellow(`No devcoach UI is running on port ${t}`));return}s(t,{onReady:a=>{o.open&&xe(a);},onListenError:a=>{console.error(a.message),o.open&&a.existingUrl&&xe(a.existingUrl),process.exit(a.exitCode);}});}),e$1.command("setup").description("First-run wizard: import backup or build your knowledge profile").action(An),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 ct(){let e=Mn();try{await e.parseAsync(process.argv);}catch(o){let t=o.code??"";if(!t.startsWith("commander."))throw o;if(t==="commander.helpDisplayed"||t==="commander.help"||t==="commander.version")return;process.exit(2);}}export{ct 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-QFIAOASG.js';import'./chunk-YMXRXYRU.js';
|