@contextq/cli 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +52 -43
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,53 +1,56 @@
1
1
  #!/usr/bin/env node
2
- var nn=Object.defineProperty;var a=(e,t)=>nn(e,"name",{value:t,configurable:!0});var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var de=(e,t)=>{for(var n in t)nn(e,n,{get:t[n],enumerable:!0})};function on(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Io(e){if(!e)return"unknown";let t=on(e);if(!t)return"unknown";let n=[...Oo].sort((o,r)=>r[0].length-o[0].length);for(let[o,r]of n)if(t===o||t.startsWith(`${o}-`))return r;return"unknown"}function No(e){if(!e)return;let t=on(e);return Ro.find(n=>n===t)}function rn(e={}){let t=e.env??(typeof process<"u"?process.env:{}),n=t.CLAUDE_CODE_SESSION_ID||void 0,o=a((s,i)=>({host:s,supportsFunctionHooks:s==="claude-code"&&t[Ao]==="1",...s==="claude-code"&&n?{sessionId:n}:{},via:i}),"finish");if(e.forcedHost)return o(e.forcedHost,"forced");let r=No(t[To]);return r?o(r,"env-override"):e.clientName!==void 0?o(Io(e.clientName),"client-info"):t.CLAUDECODE==="1"?o("claude-code","env"):o("unknown","none")}var To,Ao,Ro,Oo,sn=C(()=>{"use strict";To="CONTEXTQ_HOST",Ao="CLAUDE_CODE_ENABLE_FUNCTION_HOOKS",Ro=["claude-code","claude-desktop","codex","opencode","cyberine","muse","unknown"],Oo=[["claude-code","claude-code"],["claude-desktop","claude-desktop"],["claude-ai","claude-desktop"],["claude","claude-desktop"],["codex","codex"],["opencode","opencode"],["cyberine","cyberine"],["muse","muse"],["muse-code","muse"]];a(on,"normalizeName");a(Io,"hostFromClientName");a(No,"parseHostOverride");a(rn,"resolveHost")});import{chmodSync as Do,existsSync as qo,mkdirSync as Ho,readFileSync as Uo,writeFileSync as jo}from"fs";import{execFileSync as an}from"child_process";import{basename as Ko,dirname as cn,join as Ie}from"path";function ue(e,t,n){return X[e].configDir(t,n)}function ft(e,t,n){return Ie(ue(e,t,n),X[e].configFile)}function $(e){return`${e.replace(/\/+$/,"")}/mcp`}function pe(e){let t=cn(e);try{if(an("git",["-C",t,"rev-parse","--is-inside-work-tree"],{stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()!=="true")return!1}catch{return!1}try{return an("git",["-C",t,"check-ignore","-q","--",Ko(e)],{stdio:"ignore"}),!1}catch(n){return n.status===1}}function ln(e,t){let n=X[e],o=ft(e,t.homeDir,t.env),r=qo(o),s={};if(r){let c=Uo(o,"utf-8");try{let l=c.trim()===""?{}:JSON.parse(c);if(!l||typeof l!="object"||Array.isArray(l))throw new Error("top level is not an object");s=l}catch(l){return{surface:e,path:o,action:"refused",detail:`not valid JSON (${l.message}); left untouched`}}}let i=s.mcpServers??{};return typeof i!="object"||Array.isArray(i)?{surface:e,path:o,action:"refused",detail:"mcpServers is not an object; left untouched"}:i.contextq?{surface:e,path:o,action:"skipped",detail:"contextq server entry already present"}:pe(o)?{surface:e,path:o,action:"refused",detail:`sits in a git work tree and is not gitignored; writing the API key there would put it in a commit. Add it to that repo's .gitignore and re-run, or add the server by hand: ${n.label} -> ${$(t.apiUrl)}`}:(s.mcpServers={...i,contextq:n.entry($(t.apiUrl),t.apiKey)},Ho(cn(o),{recursive:!0}),jo(o,`${JSON.stringify(s,null,2)}
3
- `,{mode:384}),Do(o,384),{surface:e,path:o,action:r?"added":"created",detail:`contextq remote MCP (${$(t.apiUrl)}) with the API key in the header, file mode 600; check with \`${n.listCommand}\``})}var ee,X,Y=C(()=>{"use strict";ee=["gemini","cursor","copilot"],X={gemini:{label:"Gemini CLI",configDir:a(e=>Ie(e,".gemini"),"configDir"),configFile:"settings.json",entry:a((e,t)=>({httpUrl:e,headers:{Authorization:`Bearer ${t}`}}),"entry"),listCommand:"gemini mcp list"},cursor:{label:"Cursor",configDir:a(e=>Ie(e,".cursor"),"configDir"),configFile:"mcp.json",entry:a((e,t)=>({url:e,headers:{Authorization:`Bearer ${t}`}}),"entry"),listCommand:"cursor-agent mcp list"},copilot:{label:"Copilot CLI",configDir:a((e,t)=>t.COPILOT_HOME||Ie(e,".copilot"),"configDir"),configFile:"mcp-config.json",entry:a((e,t)=>({type:"http",url:e,headers:{Authorization:`Bearer ${t}`},tools:["*"]}),"entry"),listCommand:"copilot mcp list"}};a(ue,"hostConfigDir");a(ft,"hostMcpConfigPath");a($,"mcpEndpoint");a(pe,"gitTracksUnignored");a(ln,"wireHostMcp")});import{chmodSync as Lo,existsSync as gt,mkdirSync as Mo,readFileSync as Fo,writeFileSync as Jo}from"fs";import{execFileSync as Bo}from"child_process";import{dirname as Qo,join as ht}from"path";function mt(e,t){return ht(t.XDG_CONFIG_HOME||ht(e,".config"),"opencode")}function wt(e,t){return ht(mt(e,t),"opencode.json")}function dn(e="opencode"){try{let t=Bo(e,["--version"],{stdio:["ignore","pipe","ignore"],encoding:"utf-8",timeout:15e3}),n=/v?(\d+)\.\d+/.exec(t);return n?Number(n[1]):null}catch{return null}}function K(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function yt(e){let t=K(e.mcp);return t?K(K(t.servers)?.contextq)??K(t.contextq):null}function un(e){let t=wt(e.homeDir,e.env),n=e.major!=null&&e.major<2?"v1":"v2",o=$(e.apiUrl),r=`add the server by hand: OpenCode -> ${o}`;if(!gt(t)&&gt(`${t}c`))return{path:`${t}c`,action:"refused",shape:n,detail:`only opencode.jsonc exists, which this installer does not rewrite; ${r}`};let s=gt(t),i={};if(s){let d=Fo(t,"utf-8");try{let u=d.trim()===""?{}:JSON.parse(d),f=K(u);if(!f)throw new Error("top level is not an object");i=f}catch(u){return{path:t,action:"refused",shape:n,detail:`not valid JSON (${u.message}); left untouched`}}}if(i.mcp!==void 0&&!K(i.mcp))return{path:t,action:"refused",shape:n,detail:"mcp is not an object; left untouched"};if(yt(i))return{path:t,action:"skipped",shape:n,detail:"contextq server entry already present"};if(pe(t))return{path:t,action:"refused",shape:n,detail:`sits in a git work tree and is not gitignored; writing the API key there would put it in a commit. Add it to that repo's .gitignore and re-run, or ${r}`};let c={Authorization:`Bearer ${e.apiKey}`},l=K(i.mcp)??{};if(n==="v2"){let d=K(l.servers)??{};i.mcp={...l,servers:{...d,contextq:{type:"remote",url:o,oauth:!1,headers:c}}}}else i.mcp={...l,contextq:{type:"remote",url:o,enabled:!0,headers:c}};return s||(i={$schema:"https://opencode.ai/config.json",...i}),Mo(Qo(t),{recursive:!0}),Jo(t,`${JSON.stringify(i,null,2)}
4
- `,{mode:384}),Lo(t,384),{path:t,action:s?"added":"created",shape:n,detail:`contextq remote MCP (${o}) in the OpenCode ${n} shape with the API key in the header, file mode 600; check with \`opencode mcp list\``}}var Ne=C(()=>{"use strict";Y();a(mt,"opencodeConfigDir");a(wt,"opencodeConfigPath");a(dn,"detectOpencodeMajor");a(K,"asObject");a(yt,"opencodeContextqEntry");a(un,"wireOpencodeMcp")});import{existsSync as fe}from"fs";import{execSync as Vo}from"child_process";import{homedir as pn}from"os";import{join as kt}from"path";function Xo(e){try{return Vo("git rev-parse --show-toplevel",{cwd:e,stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()}catch{return""}}function Yo(e){return fe(kt(e,".codex"))}function Go(e){return fe(kt(e,".codex"))}function ge(e,t={}){let n=Xo(e),o=n||e,{host:r}=rn(t.hostSignals);if(t.forcedSurface)return{surfaces:[t.forcedSurface],root:o,host:r};let c=fe(kt(e,".claude"))||!!n&&!(r!=="unknown"&&r!=="claude-code"),l=Yo(o)||Go(t.homeDir??pn()),d=[];c&&d.push("claude-code"),l&&d.push("codex");let u=t.homeDir??pn(),f=t.env??process.env;for(let p of ee)fe(ue(p,u,f))&&d.push(p);return fe(mt(u,f))&&d.push("opencode"),d.length===0&&d.push("desktop"),{surfaces:d,root:c||l?o:e,host:r}}var xt=C(()=>{"use strict";sn();Y();Ne();a(Xo,"gitRoot");a(Yo,"hasCodexProjectDir");a(Go,"hasCodexHome");a(ge,"detectSurface")});import{chmodSync as bt,copyFileSync as zo,existsSync as qe,mkdirSync as Wo,readFileSync as Zo,writeFileSync as er}from"fs";import{homedir as tr}from"os";import{join as L}from"path";function $t(){let e=process.env.CTQ_CRED_DIR||process.env.CONTEXTQ_CRED_DIR;if(e)return e;let t=nr.map(or);return t.find(n=>qe(L(n,"credentials")))??t[0]}function or(e){let t=tr();if(process.platform==="darwin")return L(t,"Library","Application Support",e);if(process.platform==="win32"){let o=process.env.APPDATA??L(t,"AppData","Roaming");return L(o,e)}let n=process.env.XDG_CONFIG_HOME??L(t,".config");return L(n,e)}function A(){return L($t(),"credentials")}function St(){return qe(A())}function rr(e,t){return e.apiKey===t.apiKey&&e.apiUrl===t.apiUrl}function sr(){let e=new Date,t=a(n=>String(n).padStart(2,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}Z`}function ir(){let e=A();if(!qe(e))return null;let t=$t(),n=L(t,`credentials.bak-${sr()}`);return zo(e,n),bt(n,384),n}function De(e,t){if(!qe(t))return"";try{let n=Zo(t,"utf-8");for(let o of n.split(`
5
- `))if(o.startsWith(`${e}=`))return o.slice(e.length+1).trim()}catch{}return""}function N(){let e=A(),t=De("CTQ_API_KEY",e)||De("CONTEXT_API_KEY",e),n=De("CTQ_API_URL",e)||De("CONTEXT_API_URL",e);return{apiKey:t,apiUrl:n}}function fn(e){let t=$t(),n=A();Wo(t,{recursive:!0,mode:448}),bt(t,448);let o=`CTQ_API_KEY=${e.apiKey}
2
+ var xn=Object.defineProperty;var a=(e,t)=>xn(e,"name",{value:t,configurable:!0});var S=(e,t)=>()=>(e&&(t=e(e=0)),t);var he=(e,t)=>{for(var n in t)xn(e,n,{get:t[n],enumerable:!0})};function vn(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function dr(e){if(!e)return"unknown";let t=vn(e);if(!t)return"unknown";let n=[...lr].sort((o,r)=>r[0].length-o[0].length);for(let[o,r]of n)if(t===o||t.startsWith(`${o}-`))return r;return"unknown"}function ur(e){if(!e)return;let t=vn(e);return cr.find(n=>n===t)}function bn(e={}){let t=e.env??(typeof process<"u"?process.env:{}),n=t.CLAUDE_CODE_SESSION_ID||void 0,o=a((s,i)=>({host:s,supportsFunctionHooks:s==="claude-code"&&t[ar]==="1",...s==="claude-code"&&n?{sessionId:n}:{},via:i}),"finish");if(e.forcedHost)return o(e.forcedHost,"forced");let r=ur(t[ir]);return r?o(r,"env-override"):e.clientName!==void 0?o(dr(e.clientName),"client-info"):t.CLAUDECODE==="1"?o("claude-code","env"):o("unknown","none")}var ir,ar,cr,lr,$n=S(()=>{"use strict";ir="CONTEXTQ_HOST",ar="CLAUDE_CODE_ENABLE_FUNCTION_HOOKS",cr=["claude-code","claude-desktop","codex","opencode","cyberine","muse","unknown"],lr=[["claude-code","claude-code"],["claude-desktop","claude-desktop"],["claude-ai","claude-desktop"],["claude","claude-desktop"],["codex","codex"],["opencode","opencode"],["cyberine","cyberine"],["muse","muse"],["muse-code","muse"]];a(vn,"normalizeName");a(dr,"hostFromClientName");a(ur,"parseHostOverride");a(bn,"resolveHost")});import{chmodSync as pr,existsSync as fr,mkdirSync as gr,readFileSync as hr,writeFileSync as mr}from"fs";import{execFileSync as Sn}from"child_process";import{basename as wr,dirname as Cn,join as Le}from"path";function bt(e,t){if(!e||typeof e!="object"||Array.isArray(e))return"entry is not an object";let n=e,o=[];for(let[r,s]of Object.entries(t))if(En.has(r)){let i=n[r],c=i&&typeof i=="object"?i.Authorization:void 0;(typeof c!="string"||!/^Bearer\s+\S+/.test(c))&&o.push(`${r}.Authorization missing or not a bearer key`)}else JSON.stringify(n[r])!==JSON.stringify(s)&&o.push(`${r} differs`);return o.length>0?o.join("; "):null}function $t(e,t){if(!e||typeof e!="object"||Array.isArray(e))return t;let n={...e};for(let[o,r]of Object.entries(t))if(En.has(o)){let s=n[o],i=s&&typeof s=="object"&&!Array.isArray(s)?s:{};n[o]={...i,Authorization:r.Authorization}}else n[o]=r;return n}function St(e,t,n,o){return bt(t,M[e].entry($(n),o))}function me(e,t,n){return M[e].configDir(t,n)}function Ke(e,t,n){return Le(me(e,t,n),M[e].configFile)}function $(e){return`${e.replace(/\/+$/,"")}/mcp`}function we(e){let t=Cn(e);try{if(Sn("git",["-C",t,"rev-parse","--is-inside-work-tree"],{stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()!=="true")return!1}catch{return!1}try{return Sn("git",["-C",t,"check-ignore","-q","--",wr(e)],{stdio:"ignore"}),!1}catch(n){return n.status===1}}function _n(e,t){let n=M[e],o=Ke(e,t.homeDir,t.env),r=fr(o),s={};if(r){let u=hr(o,"utf-8");try{let d=u.trim()===""?{}:JSON.parse(u);if(!d||typeof d!="object"||Array.isArray(d))throw new Error("top level is not an object");s=d}catch(d){return{surface:e,path:o,action:"refused",detail:`not valid JSON (${d.message}); left untouched`}}}let i=s.mcpServers??{};if(typeof i!="object"||Array.isArray(i))return{surface:e,path:o,action:"refused",detail:"mcpServers is not an object; left untouched"};let c=!!i.contextq;if(c){let u=St(e,i.contextq,t.apiUrl,t.apiKey);if(u===null)return{surface:e,path:o,action:"skipped",detail:"contextq server entry up to date"};if(t.refresh!==!0)return{surface:e,path:o,action:"stale",detail:`contextq server entry differs (${u}); run init --refresh to update`}}if(we(o))return{surface:e,path:o,action:"refused",detail:`sits in a git work tree and is not gitignored; writing the API key there would put it in a commit. Add it to that repo's .gitignore and re-run, or add the server by hand: ${n.label} -> ${$(t.apiUrl)}`};let l=n.entry($(t.apiUrl),t.apiKey);return s.mcpServers={...i,contextq:c?$t(i.contextq,l):l},gr(Cn(o),{recursive:!0}),mr(o,`${JSON.stringify(s,null,2)}
3
+ `,{mode:384}),pr(o,384),{surface:e,path:o,action:c?"updated":r?"added":"created",detail:`contextq remote MCP (${$(t.apiUrl)}) with the API key in the header, file mode 600; check with \`${n.listCommand}\``}}var oe,M,En,z=S(()=>{"use strict";oe=["gemini","cursor","copilot"],M={gemini:{label:"Gemini CLI",configDir:a(e=>Le(e,".gemini"),"configDir"),configFile:"settings.json",entry:a((e,t)=>({httpUrl:e,headers:{Authorization:`Bearer ${t}`}}),"entry"),listCommand:"gemini mcp list"},cursor:{label:"Cursor",configDir:a(e=>Le(e,".cursor"),"configDir"),configFile:"mcp.json",entry:a((e,t)=>({url:e,headers:{Authorization:`Bearer ${t}`}}),"entry"),listCommand:"cursor-agent mcp list"},copilot:{label:"Copilot CLI",configDir:a((e,t)=>t.COPILOT_HOME||Le(e,".copilot"),"configDir"),configFile:"mcp-config.json",entry:a((e,t)=>({type:"http",url:e,headers:{Authorization:`Bearer ${t}`},tools:["*"]}),"entry"),listCommand:"copilot mcp list"}},En=new Set(["headers","http_headers"]);a(bt,"ownedEntryDiff");a($t,"mergeOwnedFields");a(St,"hostMcpEntryDiff");a(me,"hostConfigDir");a(Ke,"hostMcpConfigPath");a($,"mcpEndpoint");a(we,"gitTracksUnignored");a(_n,"wireHostMcp")});import{chmodSync as yr,existsSync as Ct,mkdirSync as kr,readFileSync as xr,writeFileSync as vr}from"fs";import{execFileSync as br}from"child_process";import{dirname as $r,join as Et}from"path";function _t(e,t){return Et(t.XDG_CONFIG_HOME||Et(e,".config"),"opencode")}function Pt(e,t){return Et(_t(e,t),"opencode.json")}function Pn(e="opencode"){try{let t=br(e,["--version"],{stdio:["ignore","pipe","ignore"],encoding:"utf-8",timeout:15e3}),n=/v?(\d+)\.\d+/.exec(t);return n?Number(n[1]):null}catch{return null}}function F(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Tt(e){let t=F(e.mcp);return t?F(F(t.servers)?.contextq)??F(t.contextq):null}function Tn(e){let t=Pt(e.homeDir,e.env),n=e.major!=null&&e.major<2?"v1":"v2",o=$(e.apiUrl),r=`add the server by hand: OpenCode -> ${o}`;if(!Ct(t)&&Ct(`${t}c`))return{path:`${t}c`,action:"refused",shape:n,detail:`only opencode.jsonc exists, which this installer does not rewrite; ${r}`};let s=Ct(t),i={};if(s){let u=xr(t,"utf-8");try{let d=u.trim()===""?{}:JSON.parse(u),f=F(d);if(!f)throw new Error("top level is not an object");i=f}catch(d){return{path:t,action:"refused",shape:n,detail:`not valid JSON (${d.message}); left untouched`}}}if(i.mcp!==void 0&&!F(i.mcp))return{path:t,action:"refused",shape:n,detail:"mcp is not an object; left untouched"};if(Tt(i))return{path:t,action:"skipped",shape:n,detail:"contextq server entry already present"};if(we(t))return{path:t,action:"refused",shape:n,detail:`sits in a git work tree and is not gitignored; writing the API key there would put it in a commit. Add it to that repo's .gitignore and re-run, or ${r}`};let c={Authorization:`Bearer ${e.apiKey}`},l=F(i.mcp)??{};if(n==="v2"){let u=F(l.servers)??{};i.mcp={...l,servers:{...u,contextq:{type:"remote",url:o,oauth:!1,headers:c}}}}else i.mcp={...l,contextq:{type:"remote",url:o,enabled:!0,headers:c}};return s||(i={$schema:"https://opencode.ai/config.json",...i}),kr($r(t),{recursive:!0}),vr(t,`${JSON.stringify(i,null,2)}
4
+ `,{mode:384}),yr(t,384),{path:t,action:s?"added":"created",shape:n,detail:`contextq remote MCP (${o}) in the OpenCode ${n} shape with the API key in the header, file mode 600; check with \`opencode mcp list\``}}var Me=S(()=>{"use strict";z();a(_t,"opencodeConfigDir");a(Pt,"opencodeConfigPath");a(Pn,"detectOpencodeMajor");a(F,"asObject");a(Tt,"opencodeContextqEntry");a(Tn,"wireOpencodeMcp")});import{existsSync as ye}from"fs";import{execSync as Sr}from"child_process";import{homedir as An}from"os";import{join as At}from"path";function Cr(e){try{return Sr("git rev-parse --show-toplevel",{cwd:e,stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()}catch{return""}}function Er(e){return ye(At(e,".codex"))}function _r(e){return ye(At(e,".codex"))}function ke(e,t={}){let n=Cr(e),o=n||e,{host:r}=bn(t.hostSignals);if(t.forcedSurface)return{surfaces:[t.forcedSurface],root:o,host:r};let c=ye(At(e,".claude"))||!!n&&!(r!=="unknown"&&r!=="claude-code"),l=Er(o)||_r(t.homeDir??An()),u=[];c&&u.push("claude-code"),l&&u.push("codex");let d=t.homeDir??An(),f=t.env??process.env;for(let p of oe)ye(me(p,d,f))&&u.push(p);return ye(_t(d,f))&&u.push("opencode"),u.length===0&&u.push("desktop"),{surfaces:u,root:c||l?o:e,host:r}}var Rt=S(()=>{"use strict";$n();z();Me();a(Cr,"gitRoot");a(Er,"hasCodexProjectDir");a(_r,"hasCodexHome");a(ke,"detectSurface")});import{chmodSync as It,copyFileSync as Pr,existsSync as Je,mkdirSync as Tr,readFileSync as Ar,writeFileSync as Rr}from"fs";import{homedir as Or}from"os";import{join as J}from"path";function Nt(){let e=process.env.CTQ_CRED_DIR||process.env.CONTEXTQ_CRED_DIR;if(e)return e;let t=Ir.map(Nr);return t.find(n=>Je(J(n,"credentials")))??t[0]}function Nr(e){let t=Or();if(process.platform==="darwin")return J(t,"Library","Application Support",e);if(process.platform==="win32"){let o=process.env.APPDATA??J(t,"AppData","Roaming");return J(o,e)}let n=process.env.XDG_CONFIG_HOME??J(t,".config");return J(n,e)}function A(){return J(Nt(),"credentials")}function Dt(){return Je(A())}function Dr(e,t){return e.apiKey===t.apiKey&&e.apiUrl===t.apiUrl}function qr(){let e=new Date,t=a(n=>String(n).padStart(2,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}Z`}function Hr(){let e=A();if(!Je(e))return null;let t=Nt(),n=J(t,`credentials.bak-${qr()}`);return Pr(e,n),It(n,384),n}function Fe(e,t){if(!Je(t))return"";try{let n=Ar(t,"utf-8");for(let o of n.split(`
5
+ `))if(o.startsWith(`${e}=`))return o.slice(e.length+1).trim()}catch{}return""}function H(){let e=A(),t=Fe("CTQ_API_KEY",e)||Fe("CONTEXT_API_KEY",e),n=Fe("CTQ_API_URL",e)||Fe("CONTEXT_API_URL",e);return{apiKey:t,apiUrl:n}}function Rn(e){let t=Nt(),n=A();Tr(t,{recursive:!0,mode:448}),It(t,448);let o=`CTQ_API_KEY=${e.apiKey}
6
6
  CTQ_API_URL=${e.apiUrl}
7
7
  CONTEXT_API_KEY=${e.apiKey}
8
8
  CONTEXT_API_URL=${e.apiUrl}
9
- `;return er(n,o,{mode:384}),bt(n,384),n}function M(e){let t=N(),n=St(),o=a(r=>{if(!n)return fn(r),{action:"created",backupPath:null};if(rr(t,r))return{action:"skipped",backupPath:null};let s=ir();return fn(r),{action:"rotated",backupPath:s}},"writeSafely");if(e.explicitKey){if(!I.test(e.explicitKey))throw new Error("the provided API key does not look valid (expected sk_live_<40 hex chars>)");let r=e.explicitKey,s=o({apiKey:r,apiUrl:e.apiUrl}),i=s.action==="skipped"?`${vt} credentials (unchanged)`:s.action==="rotated"?`${hn} credentials (${A()}; previous saved to ${s.backupPath})`:`${gn} credentials (${A()}, mode 600)`;return{apiKey:r,credLine:i,action:s.action,backupPath:s.backupPath}}if(n&&t.apiKey){let r=e.ambientVarName?`${vt} credentials (already configured at ${A()}; env ${e.ambientVarName} ignored, pass --key to rotate)`:`${vt} credentials (already configured at ${A()}; pass --key to rotate)`;return{apiKey:t.apiKey,credLine:r,action:"skipped",backupPath:null}}if(e.ambientKey){if(!I.test(e.ambientKey))throw new Error("the provided API key does not look valid (expected sk_live_<40 hex chars>)");let r=e.ambientKey,s=o({apiKey:r,apiUrl:e.apiUrl}),i=s.action==="rotated"?`${hn} credentials (${A()}; previous saved to ${s.backupPath})`:`${gn} credentials (${A()}, mode 600)`;return{apiKey:r,credLine:i,action:s.action,backupPath:s.backupPath}}return{apiKey:"",credLine:"",action:null,backupPath:null}}var nr,I,gn,hn,vt,G=C(()=>{"use strict";a($t,"platformCredDir");nr=["contextq","ctq-brain"];a(or,"platformCredDirFor");a(A,"platformCredFile");a(St,"credsFileExists");a(rr,"credsEqual");a(sr,"utcTimestamp");a(ir,"backupCredsFile");a(De,"readEnvVal");a(N,"readExistingCreds");a(fn,"writeCreds");I=/^sk_live_[0-9a-f]{40}$/,gn="[create]",hn="[rotate]",vt="[skip] ";a(M,"decideCredentialWrite")});import{existsSync as fr,readFileSync as gr,writeFileSync as hr}from"fs";import{execFileSync as mr}from"child_process";import{join as wr}from"path";function yr(e,t){try{return mr("git",["check-ignore","-q","--",t],{cwd:e,stdio:"ignore"}),!0}catch(n){return n.status===1?!1:null}}function Ct(e){return wr(e,".mcp.json")}function bn(e,t,n,o){let r=Ct(e),s=fr(r),i={};if(s){let c=gr(r,"utf-8");try{i=c.trim()===""?{}:JSON.parse(c)}catch(l){throw new Error(`.mcp.json exists but is not valid JSON (${l.message}) -- refusing to touch it`)}}if(i.mcpServers??={},i.mcpServers.contextq)return{path:r,action:"skipped",detail:"contextq server entry already present"};if(n){let c=yr(e,".mcp.json");if(c===!1)throw new Error('.mcp.json is not covered by .gitignore in this repo -- refusing to write the raw API key into a file git would track. Add ".mcp.json" to .gitignore first, or re-run without --embed-key to use the safer ${CONTEXT_API_KEY} placeholder instead.');c===null&&console.log("[warn] could not verify .mcp.json is gitignored (no git repo detected at this path) -- writing the raw key anyway.")}return i.mcpServers.contextq={command:"npx",args:["-y","@contextq/mcp"],env:{CONTEXT_API_URL:t,CONTEXT_API_KEY:n?o:"${CONTEXT_API_KEY}"}},hr(r,`${JSON.stringify(i,null,2)}
10
- `),{path:r,action:s?"added":"created",detail:`contextq server wired (key=${n?"embedded literal":"${CONTEXT_API_KEY} placeholder"})`}}var _t=C(()=>{"use strict";a(yr,"isPathGitIgnored");a(Ct,"mcpJsonPath");a(bn,"wireMcpJson")});import{appendFileSync as kr,chmodSync as xr,existsSync as Ue,mkdirSync as vr,readFileSync as Cn,writeFileSync as br}from"fs";import{execFileSync as _n}from"child_process";import{dirname as $r,join as je}from"path";import{parse as $n,stringify as Sr}from"smol-toml";function Et(e){return je(e,".codex","config.toml")}function Cr(e,t){if(!Ue(je(e,".git")))return!1;try{return _n("git",["-C",e,"ls-files","--error-unmatch","--",t],{stdio:"ignore"}),!0}catch{return!1}}function _r(e,t){try{return _n("git",["-C",e,"check-ignore","-q","--",t],{stdio:"ignore"}),!0}catch{return!1}}function Er(e){if(!Ue(je(e,".git"))||_r(e,Ke))return;let t=je(e,".gitignore"),n=Ue(t)?Cn(t,"utf-8"):"",o=n===""||n.endsWith(`
9
+ `;return Rr(n,o,{mode:384}),It(n,384),n}function B(e){let t=H(),n=Dt(),o=a(r=>{if(!n)return Rn(r),{action:"created",backupPath:null};if(Dr(t,r))return{action:"skipped",backupPath:null};let s=Hr();return Rn(r),{action:"rotated",backupPath:s}},"writeSafely");if(e.explicitKey){if(!N.test(e.explicitKey))throw new Error("the provided API key does not look valid (expected sk_live_<40 hex chars>)");let r=e.explicitKey,s=o({apiKey:r,apiUrl:e.apiUrl}),i=s.action==="skipped"?`${Ot} credentials (unchanged)`:s.action==="rotated"?`${In} credentials (${A()}; previous saved to ${s.backupPath})`:`${On} credentials (${A()}, mode 600)`;return{apiKey:r,credLine:i,action:s.action,backupPath:s.backupPath}}if(n&&t.apiKey){let r=e.ambientVarName?`${Ot} credentials (already configured at ${A()}; env ${e.ambientVarName} ignored, pass --key to rotate)`:`${Ot} credentials (already configured at ${A()}; pass --key to rotate)`;return{apiKey:t.apiKey,credLine:r,action:"skipped",backupPath:null}}if(e.ambientKey){if(!N.test(e.ambientKey))throw new Error("the provided API key does not look valid (expected sk_live_<40 hex chars>)");let r=e.ambientKey,s=o({apiKey:r,apiUrl:e.apiUrl}),i=s.action==="rotated"?`${In} credentials (${A()}; previous saved to ${s.backupPath})`:`${On} credentials (${A()}, mode 600)`;return{apiKey:r,credLine:i,action:s.action,backupPath:s.backupPath}}return{apiKey:"",credLine:"",action:null,backupPath:null}}var Ir,N,On,In,Ot,W=S(()=>{"use strict";a(Nt,"platformCredDir");Ir=["contextq","ctq-brain"];a(Nr,"platformCredDirFor");a(A,"platformCredFile");a(Dt,"credsFileExists");a(Dr,"credsEqual");a(qr,"utcTimestamp");a(Hr,"backupCredsFile");a(Fe,"readEnvVal");a(H,"readExistingCreds");a(Rn,"writeCreds");N=/^sk_live_[0-9a-f]{40}$/,On="[create]",In="[rotate]",Ot="[skip] ";a(B,"decideCredentialWrite")});function R(e){return e.replace(/(https?:\/\/)[^/\s@'"]*@/g,"$1***@").replace(/(https?:\/\/[^\s'"?]*)\?[^\s'"]*/g,"$1?***").replace(/\b([A-Za-z_][A-Za-z0-9_]*)=('[^']*'|"[^"]*"|\S+)/g,"$1=***").replace(/sk_live_[0-9A-Za-z]+/g,"***").replace(/ctq_local_\S+/g,"***").replace(/Bearer\s+\S+/g,"Bearer ***")}function Ln(e){if(typeof e!="string")return"absent";try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return"unparseable"}}var Xe=S(()=>{"use strict";a(R,"redactCommand");a(Ln,"originAndPath")});import{chmodSync as Jr,existsSync as Br,readFileSync as Xr,writeFileSync as Yr}from"fs";import{execFileSync as Qr}from"child_process";import{join as Vr}from"path";function Gr(e,t){try{return Qr("git",["check-ignore","-q","--",t],{cwd:e,stdio:"ignore"}),!0}catch(n){return n.status===1?!1:null}}function qt(e){return Vr(e,".mcp.json")}function Kn(e,t,n){return{command:"npx",args:["-y","@contextq/mcp"],env:{CONTEXT_API_URL:e,CONTEXT_API_KEY:t?n:"${CONTEXT_API_KEY}"}}}function Ht(e,t,n,o){if(!e||typeof e!="object"||Array.isArray(e))return"entry is not an object";let r=e,s=Kn(t,n,o),i=[];r.command!==s.command&&i.push("command differs"),JSON.stringify(r.args)!==JSON.stringify(s.args)&&i.push("args differ");let c=r.env&&typeof r.env=="object"&&!Array.isArray(r.env)?r.env:{};c.CONTEXT_API_URL!==s.env.CONTEXT_API_URL&&i.push(`env.CONTEXT_API_URL differs (old ${Ln(c.CONTEXT_API_URL)} -> new ${s.env.CONTEXT_API_URL})`);let l=c.CONTEXT_API_KEY;return typeof l!="string"?i.push("env.CONTEXT_API_KEY missing"):zr.test(l)===n?i.push(`env.CONTEXT_API_KEY differs (${n?"placeholder where an embedded key is expected":"embedded literal where a placeholder is expected"})`):!n&&l!==s.env.CONTEXT_API_KEY&&i.push("env.CONTEXT_API_KEY placeholder differs"),i.length>0?i.join("; "):null}function Mn(e,t,n,o,r={}){let s=qt(e),i=Br(s),c={};if(i){let f=Xr(s,"utf-8");try{c=f.trim()===""?{}:JSON.parse(f)}catch(p){throw new Error(`.mcp.json exists but is not valid JSON (${p.message}) -- refusing to touch it`)}}c.mcpServers??={};let l=!!c.mcpServers.contextq;if(l){let f=Ht(c.mcpServers.contextq,t,n,o);if(f===null)return{path:s,action:"skipped",detail:"contextq server entry up to date"};if(r.refresh!==!0)return{path:s,action:"stale",detail:`contextq server entry differs (${f}); run init --refresh to update`}}if(n){let f=Gr(e,".mcp.json");if(f===!1)throw new Error('.mcp.json is not covered by .gitignore in this repo -- refusing to write the raw API key into a file git would track. Add ".mcp.json" to .gitignore first, or re-run without --embed-key to use the safer ${CONTEXT_API_KEY} placeholder instead.');f===null&&console.log("[warn] could not verify .mcp.json is gitignored (no git repo detected at this path) -- writing the raw key anyway.")}let u=Kn(t,n,o),d=l?c.mcpServers.contextq:null;return c.mcpServers.contextq=d&&typeof d=="object"&&!Array.isArray(d)?{...d,...u,env:{...d.env&&typeof d.env=="object"?d.env:{},...u.env}}:u,Yr(s,`${JSON.stringify(c,null,2)}
10
+ `,n?{mode:384}:void 0),n&&Jr(s,384),{path:s,action:l?"updated":i?"added":"created",detail:`contextq server wired (key=${n?"embedded literal":"${CONTEXT_API_KEY} placeholder"})`}}var zr,jt=S(()=>{"use strict";Xe();a(Gr,"isPathGitIgnored");a(qt,"mcpJsonPath");a(Kn,"mcpJsonEntry");zr=/^\$\{[^}]*\}$/;a(Ht,"mcpJsonEntryDiff");a(Mn,"wireMcpJson")});import{appendFileSync as Wr,chmodSync as Zr,existsSync as Ye,mkdirSync as es,readFileSync as Fn,writeFileSync as ts}from"fs";import{execFileSync as Jn}from"child_process";import{dirname as ns,join as Qe}from"path";import{parse as re,stringify as Bn}from"smol-toml";function ze(e){return Qe(e,".codex","config.toml")}function os(e,t){if(!Ye(Qe(e,".git")))return!1;try{return Jn("git",["-C",e,"ls-files","--error-unmatch","--",t],{stdio:"ignore"}),!0}catch{return!1}}function rs(e,t){try{return Jn("git",["-C",e,"check-ignore","-q","--",t],{stdio:"ignore"}),!0}catch{return!1}}function ss(e){if(!Ye(Qe(e,".git"))||rs(e,Ve))return;let t=Qe(e,".gitignore"),n=Ye(t)?Fn(t,"utf-8"):"",o=n===""||n.endsWith(`
11
11
  `)?"":`
12
- `;kr(t,`${o}${Ke}
13
- `)}function Pr(e,t){return Sr({mcp_servers:{contextq:{url:$(e),http_headers:{Authorization:`Bearer ${t}`}}}})}function Sn(e){let t=e.mcp_servers;if(!(!t||typeof t!="object"||Array.isArray(t)))return t.contextq}function En(e,t,n){let o=Et(e),r=Ue(o),s=r?Cn(o,"utf-8"):"";if(r){let l;try{l=$n(s)}catch(d){return{path:o,action:"refused",detail:`not valid TOML (${d.message.split(`
14
- `)[0]}); left untouched`}}if(Sn(l)!==void 0)return{path:o,action:"skipped",detail:"mcp_servers.contextq already present"}}if(Cr(e,Ke))return{path:o,action:"refused",detail:`is tracked by git; writing the API key there would put it in a commit. Untrack it (git rm --cached ${Ke}) and re-run, or add the server by hand: codex -> ${$(t)}`};let i=s===""?"":s.endsWith(`
12
+ `;Wr(t,`${o}${Ve}
13
+ `)}function Ut(e,t){return Bn({mcp_servers:{contextq:{url:$(e),http_headers:{Authorization:`Bearer ${t}`}}}})}function se(e){let t=e.mcp_servers;if(!(!t||typeof t!="object"||Array.isArray(t)))return t.contextq}function Ge(e,t,n){let o=se(re(Ut(t,n)));return bt(e,o)}function cs(e,t){let n=e.split(`
14
+ `),o=[],r=!1,s=0;for(;s<n.length;){if(!is.test(n[s])){o.push(n[s]),s++;continue}let i=s+1;for(;i<n.length&&!as.test(n[i]);)i++;for(;i>s+1&&n[i-1].trim()==="";)i--;for(r||(o.push(...t.replace(/\n+$/,"").split(`
15
+ `)),i<n.length&&o.push(""),r=!0),s=i;s<n.length&&n[s].trim()===""&&o[o.length-1]==="";)s++}return r?o.join(`
16
+ `):null}function Xn(e,t,n,o={}){let r=ze(e),s=Ye(r),i=s?Fn(r,"utf-8"):"",c=!1;if(s){let d;try{d=re(i)}catch(p){return{path:r,action:"refused",detail:`not valid TOML (${p.message.split(`
17
+ `)[0]}); left untouched`}}let f=se(d);if(f!==void 0){let p=Ge(f,t,n);if(p===null)return{path:r,action:"skipped",detail:"mcp_servers.contextq up to date"};if(o.refresh!==!0)return{path:r,action:"stale",detail:`mcp_servers.contextq differs (${p}); run init --refresh to update`};c=!0}}if(os(e,Ve))return{path:r,action:"refused",detail:`is tracked by git; writing the API key there would put it in a commit. Untrack it (git rm --cached ${Ve}) and re-run, or add the server by hand: codex -> ${$(t)}`};let l=i===""?"":i.endsWith(`
15
18
  `)?`
16
19
  `:`
17
20
 
18
- `,c=`${s}${i}${Pr(t,n)}`;try{let l=$n(c);if(Sn(l)===void 0)throw new Error("mcp_servers.contextq missing after merge")}catch(l){return{path:o,action:"refused",detail:`adding [mcp_servers.contextq] would not parse (${l.message.split(`
19
- `)[0]}), e.g. mcp_servers is an inline table; left untouched`}}return vr($r(o),{recursive:!0}),Er(e),br(o,c,{mode:384}),xr(o,384),{path:o,action:r?"added":"created",detail:`contextq streamable HTTP MCP (${$(t)}) with the API key in http_headers, file mode 600, gitignored; Codex reads project config only in a trusted project, so run \`codex\` once here and accept the trust prompt, then check with \`codex mcp list\``}}var Ke,Pt=C(()=>{"use strict";Y();Ke=".codex/config.toml";a(Et,"codexConfigPath");a(Cr,"gitTracked");a(_r,"gitIgnored");a(Er,"ensureConfigGitignored");a(Pr,"codexMcpBlock");a(Sn,"contextqEntry");a(En,"wireCodexMcp")});import{appendFileSync as Tr,existsSync as he,mkdirSync as Ar,readFileSync as Tt,writeFileSync as Rr}from"fs";import{execFileSync as Or}from"child_process";import{join as F,relative as Ir}from"path";function Pn(e){return`'${e.split("'").join("'\\''")}'`}function Tn(e,t,n,o=J){return`${o} hook boot --format ${n} --agent ${Pn(e)} --workspace ${Pn(t)}`}function Dr(e,t){if(!he(F(e,".git")))return!1;try{return Or("git",["-C",e,"ls-files","--error-unmatch","--",Ir(e,t)],{stdio:"ignore"}),!0}catch{return!1}}function An(e,t,n){let{path:o,dir:r,topLevelDefaults:s}=e,i=he(o),c={};if(i){let d=Tt(o,"utf-8");try{c=d.trim()===""?{}:JSON.parse(d)}catch(u){return{path:o,action:"refused",detail:`not valid JSON (${u.message}); left untouched`}}if(!c||typeof c!="object"||Array.isArray(c))return{path:o,action:"refused",detail:"top level is not a JSON object; left untouched"}}if(s)for(let[d,u]of Object.entries(s))c[d]===void 0&&(c[d]=u);return c.hooks??={},c.hooks.SessionStart??=[],c.hooks.SessionStart.some(d=>(d?.hooks??[]).some(u=>typeof u.command=="string"&&Nr.test(u.command)))?{path:o,action:"skipped",detail:"SessionStart contextq-boot hook already present"}:e.root&&Dr(e.root,o)?{path:o,action:"refused",detail:"is tracked by git; a per-machine hook would be committed for every teammate. Untrack it and re-run"}:(c.hooks.SessionStart.push({hooks:[{type:"command",command:t}]}),Ar(r,{recursive:!0}),Rr(o,`${JSON.stringify(c,null,2)}
20
- `),{path:o,action:"wired",detail:n})}function Le(e){return F(e,".claude","settings.local.json")}function Me(e){return F(e,".codex","hooks.json")}function Rn(e,t,n,o=J){let r=Le(e),s=Tn(t,n,"claude",o);return An({path:r,dir:F(e,".claude"),root:e},s,`SessionStart boot hook wired (agent=${t}, workspace=${n}; runs \`${o} hook boot\`, key from the credentials file)`)}function qr(e){let t=F(e,".codex","config.toml");if(!he(t))return!1;let n=Tt(t,"utf-8");return/^\s*\[{1,2}hooks(\.|\])/m.test(n)}function Hr(e,t){if(!he(F(e,".git")))return;let n=F(e,".gitignore"),o=he(n)?Tt(n,"utf-8"):"";if(new Set(o.split(`
21
+ `,u=`${i}${l}${Ut(t,n)}`;if(c){let d=se(re(Ut(t,n))),f=Bn({mcp_servers:{contextq:$t(se(re(i)),d)}}),p=cs(i,f),g=!1;try{g=p!==null&&Ge(se(re(p)),t,n)===null}catch{g=!1}if(p===null||!g)return{path:r,action:"refused",detail:"mcp_servers.contextq is not a plain [mcp_servers.contextq] table, so it cannot be replaced in place; update it by hand"};u=p}try{let d=re(u);if(se(d)===void 0)throw new Error("mcp_servers.contextq missing after merge")}catch(d){return{path:r,action:"refused",detail:`adding [mcp_servers.contextq] would not parse (${d.message.split(`
22
+ `)[0]}), e.g. mcp_servers is an inline table; left untouched`}}return es(ns(r),{recursive:!0}),ss(e),ts(r,u,{mode:384}),Zr(r,384),{path:r,action:c?"updated":s?"added":"created",detail:`contextq streamable HTTP MCP (${$(t)}) with the API key in http_headers, file mode 600, gitignored; Codex reads project config only in a trusted project, so run \`codex\` once here and accept the trust prompt, then check with \`codex mcp list\``}}var Ve,is,as,Lt=S(()=>{"use strict";z();Ve=".codex/config.toml";a(ze,"codexConfigPath");a(os,"gitTracked");a(rs,"gitIgnored");a(ss,"ensureConfigGitignored");a(Ut,"codexMcpBlock");a(se,"contextqEntry");a(Ge,"codexMcpEntryDiff");is=/^\s*\[\s*mcp_servers\s*\.\s*contextq\s*(\.[^\]]*)?\]\s*(#.*)?$/,as=/^\s*\[/;a(cs,"replaceContextqTables");a(Xn,"wireCodexMcp")});import{appendFileSync as ls,existsSync as ie,mkdirSync as ds,readFileSync as We,writeFileSync as us}from"fs";import{execFileSync as ps}from"child_process";import{join as X,relative as fs}from"path";function Yn(e){return`'${e.split("'").join("'\\''")}'`}function Kt(e,t,n,o=D){return`${o} hook boot --format ${n} --agent ${Yn(e)} --workspace ${Yn(t)}`}function gs(e,t){if(!ie(X(e,".git")))return!1;try{return ps("git",["-C",e,"ls-files","--error-unmatch","--",fs(e,t)],{stdio:"ignore"}),!0}catch{return!1}}function Gn(e,t,n,o){let{path:r,dir:s,topLevelDefaults:i}=e,c=ie(r),l={};if(c){let p=We(r,"utf-8");try{l=p.trim()===""?{}:JSON.parse(p)}catch(g){return{path:r,action:"refused",detail:`not valid JSON (${g.message}); left untouched`}}if(!l||typeof l!="object"||Array.isArray(l))return{path:r,action:"refused",detail:"top level is not a JSON object; left untouched"}}if(i)for(let[p,g]of Object.entries(i))l[p]===void 0&&(l[p]=g);l.hooks??={},l.hooks.SessionStart??=[];let u=[];for(let p of l.hooks.SessionStart)for(let g of p?.hooks??[])g&&typeof g.command=="string"&&Vn.test(g.command)&&u.push(g);let d=u.filter(p=>p.command!==t);if(u.length>0&&d.length===0)return{path:r,action:"skipped",detail:"SessionStart contextq-boot hook up to date"};if(d.length>0&&!o)return{path:r,action:"stale",detail:`SessionStart boot hook command differs: old \`${R(d[0].command)}\` vs new \`${R(t)}\`; run init --refresh to update`};if(e.root&&gs(e.root,r))return{path:r,action:"refused",detail:"is tracked by git; a per-machine hook would be committed for every teammate. Untrack it and re-run"};let f=d[0]?.command;if(d.length>0)for(let p of d)p.command=t;else l.hooks.SessionStart.push({hooks:[{type:"command",command:t}]});return ds(s,{recursive:!0}),us(r,`${JSON.stringify(l,null,2)}
23
+ `),f!==void 0?{path:r,action:"updated",detail:`SessionStart boot hook command replaced: old \`${R(f)}\` -> new \`${R(t)}\``}:{path:r,action:"wired",detail:n}}function xe(e){return X(e,".claude","settings.local.json")}function ve(e){return X(e,".codex","hooks.json")}function zn(e,t,n,o=D,r={}){let s=xe(e),i=Kt(t,n,"claude",o);return Gn({path:s,dir:X(e,".claude"),root:e},i,`SessionStart boot hook wired (agent=${t}, workspace=${n}; runs \`${o} hook boot\`, key from the credentials file)`,r.refresh===!0)}function hs(e){let t=X(e,".codex","config.toml");if(!ie(t))return!1;let n=We(t,"utf-8");return/^\s*\[{1,2}hooks(\.|\])/m.test(n)}function ms(e,t){if(!ie(X(e,".git")))return;let n=X(e,".gitignore"),o=ie(n)?We(n,"utf-8"):"";if(new Set(o.split(`
21
24
  `).map(i=>i.trim())).has(t))return;let s=`
22
25
  # .codex/hooks.json embeds a per-machine absolute path (the contextq boot
23
26
  # hook command) -- gitignored the same way .claude/settings.local.json is.
24
27
  # Each teammate who wants it needs to run \`npx contextq init\` themselves.
25
28
  ${t}
26
- `;Tr(n,s)}function On(e,t,n,o=J){let r=Me(e),s=Tn(t,n,"codex",o),i="Codex requires this hook to be TRUSTED before it runs -- start `codex` in this repo and approve it at the hook-trust prompt (or review/trust via `/hooks` in a session)",c=qr(e)?" -- WARNING: .codex/config.toml also declares an inline [hooks] table; Codex loads both hooks.json and the inline table and warns on every run. Prefer one representation per layer.":"",l=An({path:r,dir:F(e,".codex"),root:e,topLevelDefaults:{description:"ContextQ boot hook (managed by `npx contextq init` -- re-run the installer to update, do not hand-edit)"}},s,`SessionStart boot hook wired (agent=${t}, workspace=${n}). ${i}.${c}`);return l.action==="wired"&&Hr(e,".codex/hooks.json"),l}var Nr,J,Fe=C(()=>{"use strict";Nr=/contextq-boot\.sh|contextq.*boot/;a(Pn,"shellQuote");J="npx -y --prefer-offline @contextq/cli";a(Tn,"buildBootCommand");a(Dr,"gitTracked");a(An,"mergeSessionStartHook");a(Le,"claudeHookPath");a(Me,"codexHookPath");a(Rn,"wireSessionHook");a(qr,"hasInlineHooksTable");a(Hr,"ensureGitignored");a(On,"wireCodexSessionHook")});import{chmodSync as Ur,existsSync as At,mkdirSync as Dn,readFileSync as jr,writeFileSync as qn}from"fs";import{dirname as Hn,join as Je}from"path";function Mr(e,t=J){return`${t} hook boot --format ${e} --require-config`}function Fr(e,t,n=J){return`${n} hook snapshot --format ${e} --reason ${t} --require-config`}function Jr(e){return e==="copilot"?[{event:"sessionEnd",reason:"end"},{event:"preCompact",reason:"compact"}]:[]}function Be(e,t,n){let o=ue(e,t,n);return e==="gemini"?Je(o,"settings.json"):e==="cursor"?Je(o,"hooks.json"):Je(o,"hooks","contextq.json")}function Br(e){if(!At(e))return{doc:{}};let t=jr(e,"utf-8");try{let n=t.trim()===""?{}:JSON.parse(t);return!n||typeof n!="object"||Array.isArray(n)?{error:"top level is not a JSON object"}:{doc:n}}catch(n){return{error:`not valid JSON (${n.message})`}}}function In(e,t){let n=e.hooks??={};if(typeof n!="object"||Array.isArray(n))return;let o=n[t]??=[];return Array.isArray(o)?o:void 0}function Nn(e,t=Kr){return JSON.stringify(e).match(t)!==null}function Un(e,t){let n=Be(e,t.homeDir,t.env),o=At(n),{doc:r,error:s}=Br(n);if(!r)return{surface:e,path:n,action:"refused",detail:`${s}; left untouched`};let i=Mr(e,t.runner),c=e==="gemini"?"SessionStart":"sessionStart";e!=="gemini"&&(r.version??=1);let l=In(r,c);if(!l)return{surface:e,path:n,action:"refused",detail:`hooks.${c} is not an array; left untouched`};let d=Nn(l),u=[];for(let{event:g,reason:h}of Jr(e)){let w=In(r,g);if(!w)return{surface:e,path:n,action:"refused",detail:`hooks.${g} is not an array; left untouched`};Nn(w,Lr)||u.push({list:w,reason:h,event:g})}if(d&&u.length===0)return{surface:e,path:n,action:"skipped",detail:"contextq hooks already present"};if(pe(n))return{surface:e,path:n,action:"refused",detail:`sits in a git work tree and is not gitignored; add the SessionStart hook by hand: ${i}`};d||(e==="gemini"?l.push({hooks:[{type:"command",name:"contextq-boot",command:i,timeout:3e4}]}):e==="cursor"?l.push({command:i,timeout:30}):l.push({type:"command",bash:i,timeoutSec:30}));for(let g of u)g.list.push({type:"command",bash:Fr(e,g.reason,t.runner),timeoutSec:30});Dn(Hn(n),{recursive:!0}),qn(n,`${JSON.stringify(r,null,2)}
27
- `,{mode:384}),Ur(n,384);let f=`SessionStart boot hook (runs only in repos with a .contextq/config.json); ${e==="cursor"?"Cursor runs sessionStart without waiting, so the first turn may not see it":"the boot pack is injected at session start"}`,p=u.length>0?`${u.map(g=>g.event).join(" + ")} snapshot hook (git state only, no transcript; respects capture_scope)`:"";return{surface:e,path:n,action:o?"added":"created",detail:[d?"":f,p].filter(Boolean).join("; ")}}function jn(e,t,n){let o=Je(e,".contextq","config.json");return At(o)?{path:o,action:"skipped",detail:"repo already bound -- left untouched"}:(Dn(Hn(o),{recursive:!0}),qn(o,`${JSON.stringify({config_schema_version:5,workspace:t,agent:n,read_scope:"own+global"},null,2)}
28
- `),{path:o,action:"created",detail:`binds this repo to workspace ${t} for the host hooks; commit it so other machines share the binding`})}var Kr,Lr,Rt=C(()=>{"use strict";Y();Fe();Kr=/contextq.*hook boot|contextq-boot\.sh/,Lr=/contextq.*hook snapshot/;a(Mr,"hostHookCommand");a(Fr,"hostSnapshotCommand");a(Jr,"snapshotEvents");a(Be,"hostHookPath");a(Br,"readJsonObject");a(In,"eventList");a(Nn,"alreadyWired");a(Un,"wireHostHook");a(jn,"ensureRepoConfig")});import{spawnSync as Ot}from"child_process";import{existsSync as Qr,readFileSync as Vr}from"fs";import{join as Xr}from"path";function Ln(e){return e[It]||"claude"}function Yr(e){let t=/(\d+)\.(\d+)\.(\d+)/.exec(e);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function Gr(e,t){for(let n=0;n<3;n++)if(e[n]!==t[n])return e[n]>t[n];return!0}function Mn(e){let t=Xr(e,".mcp.json");if(!Qr(t))return!1;try{return!!JSON.parse(Vr(t,"utf-8")).mcpServers?.contextq}catch{return!1}}function Nt(e=process.env){let t=Ot(Ln(e),["--version"],{encoding:"utf-8",env:e});if(t.error||t.status!==0)return{reason:"the `claude` binary was not found on PATH"};let n=Yr(t.stdout);return n?Gr(n,Kn)?{version:n.join(".")}:{reason:`Claude Code ${n.join(".")} is older than ${Kn.join(".")}, the ContextQ plugin's floor (its function-hook module needs prompt.edit and ui.render AbovePrompt)`}:{reason:`could not read a version from \`claude --version\` (${t.stdout.trim()})`}}function zr(e){let t=e.split(`
29
- `).map(n=>n.trim()).filter(n=>n.startsWith("{"));for(let n=t.length-1;n>=0;n--)try{return JSON.parse(t[n])}catch{continue}return null}function Wr(e){return`${e.replace(/\/$/,"")}/plugin/marketplace.json`}function Qe(e,t,n,o,r=process.env,s={}){let i=Nt(r);if("reason"in i)return{status:"unavailable",reason:i.reason};let c=Ln(r),l=Ot(c,["plugin","marketplace","add",Wr(s.marketplaceBase??t),"--scope","project"],{cwd:e,encoding:"utf-8",env:r});if(l.error||l.status!==0)return{status:"failed",reason:`\`claude plugin marketplace add\` exited ${l.status??"with an error"}`};let d=["plugin","install",_,"--scope","project","--json","--config",`apiKey=${n}`];t.replace(/\/$/,"")!==o.replace(/\/$/,"")&&d.push("--config",`apiUrl=${t}`);let u=Ot(c,d,{cwd:e,encoding:"utf-8",env:r}),f=zr(u.stdout??"");if(!f)return{status:"failed",reason:`\`claude plugin install --json\` printed no result line (exit ${u.status??"error"})`};if(f.outcome!=="ok"||u.status!==0){let g=typeof f.failureCode=="string"?` [${f.failureCode}]`:"";return{status:"failed",reason:`${String(f.message??"install failed")}${g}`}}let p=String(f.message??"installed");return{status:"installed",message:p,alreadyInstalled:/already installed/i.test(p)}}function Fn(e){return e.noPlugin?{delegate:!1,reason:"--no-plugin: wiring Claude Code through .mcp.json"}:e.legacyMcpEntry?{delegate:!1,reason:".mcp.json already wires contextq -- keeping that path, not installing the plugin on top of it (both would register the same MCP server)"}:e.verifyStatus===401||e.verifyStatus===403?{delegate:!1,reason:`the server rejected this key (HTTP ${e.verifyStatus}) -- not handing it to the plugin, whose apiKey is stored machine-wide and would replace a working one; wiring .mcp.json instead`}:{delegate:!0}}var _,Kn,It,me=C(()=>{"use strict";_="contextq@contextq-plugins",Kn=[2,1,281],It="CONTEXTQ_CLAUDE_BIN";a(Ln,"claudeBin");a(Yr,"parseClaudeVersion");a(Gr,"versionAtLeast");a(Mn,"hasLegacyMcpEntry");a(Nt,"detectClaude");a(zr,"lastJsonLine");a(Wr,"marketplaceUrl");a(Qe,"installPlugin");a(Fn,"decidePluginDelegation")});function te(e,t){if(!Zr.test(e))throw new Error(`${t} value "${e}" is not a valid slug -- allowed: ${Ve}`);return e}function B(e){let t=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"");return t=t.slice(0,64),t=t.replace(/[-_]+$/g,""),t===""?"default":t}var Zr,Ve,Xe=C(()=>{"use strict";Zr=/^[a-z0-9][a-z0-9_-]{0,63}$/,Ve='lowercase letters, digits, "-", "_" (must start with a letter/digit, max 64 chars)';a(te,"validateSlug");a(B,"sanitizeSlugFallback")});import{execFileSync as es}from"child_process";import{existsSync as Jn,readFileSync as ts}from"fs";import{basename as Bn,join as Qn}from"path";function we(e){let t=(e??"").trim();return!t||/^\$\{[^}]*\}$/.test(t)?"":t}function ke(e){let t=N(),n=we(e.CONTEXT_API_KEY)||we(e.CTQ_API_KEY),o=n||t.apiKey,r=(n?we(e.CONTEXT_API_URL)||we(e.CTQ_API_URL):"")||t.apiUrl||we(e.CONTEXT_API_URL)||ns;return{apiKey:o,apiUrl:r.replace(/\/+$/,"")}}function Dt(e,t){if(!t||t.trim()==="")return e;try{let n=JSON.parse(t);if(typeof n.cwd=="string"&&n.cwd)return n.cwd;if(Array.isArray(n.workspace_roots)&&typeof n.workspace_roots[0]=="string")return n.workspace_roots[0]}catch{return e}return e}function q(e,t){try{return es("git",["-C",e,...t],{stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()}catch{return""}}function qt(e,t,n){let o=Qn(e,".contextq","config.json");if(Jn(o))return ts(o,"utf-8");let r=B(Bn(e));return JSON.stringify({workspace:(n||r).toLowerCase(),agent:t||r,read_scope:"own+global"})}function Ye(e,t){return JSON.stringify(e==="cursor"?{additional_context:t}:e==="copilot"?{additionalContext:t}:{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:t}})}async function Vn(e){let t=Dt(e.cwd,e.stdinText),n=q(t,["rev-parse","--show-toplevel"]);if(!n||e.requireConfig&&!Jn(Qn(n,".contextq","config.json")))return"";let{apiKey:o,apiUrl:r}=ke(e.env);if(!o)return Ye(e.format,"ContextQ: no API key found (CONTEXT_API_KEY or the credentials file `npx @contextq/cli login` or `init` writes), so no memory was loaded this session. Fix: run npx @contextq/cli login once on this machine.");let s=new URLSearchParams({config:qt(n,e.agent,e.workspace),agent_slug:e.agent??"",project:Bn(n),git_root:n,branch:q(n,["branch","--show-current"]),head_sha:q(n,["rev-parse","--short","HEAD"]),goal:"",board:"",token_budget:"3000"}),i=e.fetchImpl??fetch,c=0,l="";try{let d=await i(`${r}/api/agent/hooks/session-start`,{method:"POST",headers:{Authorization:`Bearer ${o}`,Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:s.toString(),signal:AbortSignal.timeout(e.timeoutMs??1e4)});c=d.status,l=await d.text()}catch{c=0}if(c===401||c===403)return Ye(e.format,`ContextQ: ${r} refused the stored API key (HTTP ${c}): it is expired, revoked or wrong, so no memory was loaded and nothing will be saved this session. Fix: npx @contextq/cli init --key <new sk_live_ key>, or run init from a terminal to be prompted.`);if(c===0||c>=500)return Ye(e.format,`ContextQ: ${r} is unreachable or failing (HTTP ${c}), so no memory was loaded this session. The key was not checked; memory resumes when the server answers again.`);if(c<200||c>=300||l.trim()==="")return"";try{let u=JSON.parse(l).hookSpecificOutput?.additionalContext;return typeof u!="string"||u===""?"":Ye(e.format,u)}catch{return""}}async function Ht(e=500){return process.stdin.isTTY?"":new Promise(t=>{let n="",o=a(()=>{process.stdin.removeAllListeners("data"),process.stdin.removeAllListeners("end"),process.stdin.pause(),t(n)},"done"),r=setTimeout(o,e);process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{n+=s}),process.stdin.on("end",()=>{clearTimeout(r),o()})})}var ye,ns,Ge=C(()=>{"use strict";G();Xe();ye=["claude","codex","gemini","cursor","copilot"],ns="https://app.contextq.dev";a(we,"usable");a(ke,"resolveHookCreds");a(Dt,"sessionCwd");a(q,"git");a(qt,"hookConfig");a(Ye,"formatForHost");a(Vn,"runHookBoot");a(Ht,"readStdinIfPiped")});var uo={};de(uo,{DEFAULT_LOCAL_PORT:()=>so,EDGE_PACKAGE:()=>Jt,EDGE_SOURCE_ENV:()=>io,EDGE_VERSION_PIN:()=>ro,LocalRuntimeError:()=>D,MIN_NODE_MAJOR:()=>xe,STATE_DIR_ENV:()=>Mt,checkNode:()=>et,defaultLocalStateDir:()=>U,preflight:()=>lo,provision:()=>co,start:()=>Se,status:()=>oe,stop:()=>Os});import{spawn as vs,spawnSync as Lt}from"child_process";import{chmodSync as bs,closeSync as $s,existsSync as ne,mkdirSync as Ss,openSync as Cs,readFileSync as Ze,rmSync as We,writeFileSync as oo}from"fs";import{homedir as _s,platform as Es}from"os";import{join as R}from"path";function U(e=process.env){if(e[Mt])return e[Mt];let t=_s();if(Es()==="darwin")return R(t,"Library","Application Support","contextq","local");let n=e.XDG_DATA_HOME&&e.XDG_DATA_HOME.trim()!==""?e.XDG_DATA_HOME:R(t,".local","share");return R(n,"contextq","local")}function $e(e){let t=e.version??ro,n=e.stateDir??U();return{stateDir:n,runtimeDir:e.runtimeDir??R(n,"runtime"),port:e.port??so,version:t,source:e.packageSource??process.env[io]??`${Jt}@${t}`,nodeVersion:e.nodeVersion??process.versions.node,nodeBin:e.nodeBin??process.execPath,npmBin:e.npmBin??"npm",fetchImpl:e.fetchImpl??fetch,startTimeoutMs:e.startTimeoutMs??15e3}}function ao(e){return R(e.runtimeDir,`edge-${e.version}`)}function ve(e){return R(ao(e),"node_modules","@contextq","edge")}function et(e){let t=Number(e.split(".")[0]);if(!Number.isFinite(t)||t<xe)throw new D(`ContextQ local mode needs Node ${xe} or newer, and this is Node ${e}; install Node ${xe}+ and run \`contextq local start\` again.`)}function Ft(e){Ss(e,{recursive:!0,mode:448}),bs(e,448)}function no(e){let t=R(ve(e),"package.json");if(!ne(t))return null;try{return JSON.parse(Ze(t,"utf8")).version}catch{return null}}function co(e={}){let t=$e(e);et(t.nodeVersion);let n=ao(t);if(no(t)===t.version&&ne(R(ve(t),"dist","cli.js")))return{dir:ve(t),installed:!1};Ft(t.stateDir),Ft(n),ne(R(n,"package.json"))||oo(R(n,"package.json"),`${JSON.stringify({name:"contextq-local-runtime",private:!0},null,2)}
30
- `);let o=Lt(t.npmBin,["install","--no-audit","--no-fund","--omit=dev","--install-links","--prefix",n,t.source],{encoding:"utf8",timeout:3e5});if(o.error||o.status!==0){let s=`${o.stderr??""}`.trim().split(`
31
- `).slice(-3).join(" | ");throw new D(`could not install ${t.source} into ${n}${/E404|No matching version|ETARGET/.test(s)?" (that version is not published yet)":""}: ${s||o.error?.message||`npm exited ${o.status}`}`)}let r=no(t);if(r!==t.version)throw new D(`installed ${Jt} is ${r??"missing"}, but this CLI is pinned to ${t.version}; remove ${n} and run \`contextq local start\` again.`);return{dir:ve(t),installed:!0}}function lo(e={}){let t=$e(e);et(t.nodeVersion);let n=ve(t),o=Lt(t.nodeBin,["--input-type=module","-e","import Database from 'better-sqlite3'; new Database(':memory:').prepare('select 1').get();"],{cwd:n,encoding:"utf8",timeout:3e4});if(o.status!==0){let s=`${o.stderr??""}`.trim().split(`
32
- `).find(i=>/Error|error/.test(i))??`exit ${o.status}`;throw new D(`better-sqlite3 does not load on this machine (${s.slice(0,200)}); local mode supports macOS arm64 and Linux x64 with Node ${xe}+ and npm prebuilt binaries.`)}return Lt(t.nodeBin,["--input-type=module","-e","import { createRequire } from 'node:module'; import Database from 'better-sqlite3'; createRequire(import.meta.url + '/../')('sqlite-vec').load(new Database(':memory:'));"],{cwd:n,encoding:"utf8",timeout:3e4}).status!==0?{vectorSearch:!1,warning:"sqlite-vec does not load here; local search runs keyword-only (FTS5)."}:{vectorSearch:!0}}function be(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}function Ps(e){let t=H(e).pid;if(!ne(t))return null;let n=Number(Ze(t,"utf8").trim());return Number.isInteger(n)&&n>0?n:null}function Ts(e){let t=H(e).endpoint;if(!ne(t))return null;try{return JSON.parse(Ze(t,"utf8"))}catch{return null}}async function As(e,t){try{let n=await e(`${t}/health`,{signal:AbortSignal.timeout(2e3)});return n.ok?await n.json():null}catch{return null}}async function oe(e={}){let t=$e(e),n={running:!1,owned:!1,url:null,pid:null,edgeVersion:null,pinnedVersion:t.version,stateDir:t.stateDir},o=Ps(t.stateDir),r=Ts(t.stateDir);if(o===null)return{...n,note:"not started"};if(!be(o))return We(H(t.stateDir).pid,{force:!0}),{...n,note:`not running (removed a stale pidfile for pid ${o})`};if(!r||r.pid!==o)return{...n,pid:o,note:`pid ${o} is alive but has not published its endpoint`};let s=await As(t.fetchImpl,r.url);return!s||s.mode!=="local"||s.service!=="contextq-edge"?{...n,pid:o,url:r.url,note:`pid ${o} does not answer as a ContextQ local server at ${r.url}`}:{...n,running:!0,owned:!0,url:r.url,pid:o,edgeVersion:String(s.edgeVersion??"")||null}}async function Rs(e){let t=`http://127.0.0.1:${e.port}`;try{let n=await e.fetchImpl(`${t}/health`,{signal:AbortSignal.timeout(1500)});return(await n.json().catch(()=>null))?.service==="contextq-edge"?`another ContextQ local server (not started from ${e.stateDir})`:`a process that is not ContextQ (HTTP ${n.status})`}catch(n){return n.cause?.code==="ECONNREFUSED"?null:"a process that does not answer HTTP"}}async function Se(e={}){let t=$e(e);et(t.nodeVersion);let n=await oe(e);if(n.running){if(n.edgeVersion&&n.edgeVersion!==t.version)throw new D(`the running local server is edge ${n.edgeVersion}, but this CLI is pinned to ${t.version}; run \`contextq local stop\` and start it again.`);return{...n,started:!1}}let o=await Rs(t);if(o)throw new D(`port ${t.port} on 127.0.0.1 is held by ${o}; it was left alone. Stop it or pass --port <n>.`);let{dir:r}=co(e),s=lo(e);Ft(t.stateDir),We(H(t.stateDir).endpoint,{force:!0});let i=Cs(H(t.stateDir).log,"a",384),c=vs(t.nodeBin,[R(r,"dist","cli.js"),"serve","--state-dir",t.stateDir,"--port",String(t.port)],{detached:!0,stdio:["ignore",i,i]});if($s(i),c.pid===void 0)throw new D("could not start the local server process");oo(H(t.stateDir).pid,`${c.pid}
33
- `,{mode:384}),c.unref();let l=Date.now()+t.startTimeoutMs;for(;Date.now()<l;){let u=await oe(e);if(u.running)return{...u,started:!0,...s.warning?{warning:s.warning}:{}};if(!be(c.pid))break;await new Promise(f=>setTimeout(f,200))}let d=ne(H(t.stateDir).log)?Ze(H(t.stateDir).log,"utf8").trim().split(`
34
- `).slice(-3).join(" | "):"";throw be(c.pid)&&process.kill(c.pid,"SIGTERM"),We(H(t.stateDir).pid,{force:!0}),new D(`the local server did not come up within ${Math.round(t.startTimeoutMs/1e3)} s${d?`: ${d}`:""}`)}async function Os(e={}){let t=$e(e),n=await oe(e);if(!n.running||!n.owned||n.pid===null)return{stopped:!1,note:n.note??"not running"};process.kill(n.pid,"SIGTERM");let o=Date.now()+1e4;for(;Date.now()<o&&be(n.pid);)await new Promise(r=>setTimeout(r,100));return be(n.pid)?{stopped:!1,note:`pid ${n.pid} did not exit after SIGTERM; it was left running`}:(We(H(t.stateDir).pid,{force:!0}),{stopped:!0,note:`stopped pid ${n.pid}`})}var Jt,ro,so,xe,Mt,io,D,H,Ce=C(()=>{"use strict";Jt="@contextq/edge",ro="0.2.0",so=47321,xe=22,Mt="CONTEXTQ_LOCAL_STATE_DIR",io="CONTEXTQ_EDGE_PACKAGE",D=class extends Error{static{a(this,"LocalRuntimeError")}constructor(t){super(t),this.name="LocalRuntimeError"}};a(U,"defaultLocalStateDir");a($e,"resolve");H=a(e=>({pid:R(e,"edge.pid"),endpoint:R(e,"endpoint.json"),log:R(e,"edge.log")}),"paths");a(ao,"runtimeRoot");a(ve,"edgePackageDir");a(et,"checkNode");a(Ft,"ensureDir");a(no,"installedVersion");a(co,"provision");a(lo,"preflight");a(be,"pidAlive");a(Ps,"readPid");a(Ts,"readEndpoint");a(As,"health");a(oe,"status");a(Rs,"portHolder");a(Se,"start");a(Os,"stop")});var Qt={};de(Qt,{readJournal:()=>fo,readPluginState:()=>Q,reconfigurePlugin:()=>Bt,restorePlugin:()=>Ms,supportsTerminalConfigure:()=>po,syncPlatformCredentials:()=>Fs,writeJournal:()=>z});import{spawnSync as Is}from"child_process";import{chmodSync as Ns,existsSync as Ds,mkdirSync as qs,readFileSync as Hs,renameSync as Us,writeFileSync as js}from"fs";import{dirname as Ks}from"path";function Ls(e){return e[It]||"claude"}function W(e){return e.replace(/\/+$/,"")}function tt(e,t,n){return Is(Ls(e),t,{encoding:"utf-8",env:e,input:n,timeout:6e4})}function po(e=process.env){let t=tt(e,["plugin","configure","--help"]);return!t.error&&t.status===0&&/--values-stdin/.test(t.stdout??"")}function Q(e=process.env,t=_){let n=tt(e,["plugin","list","--json"]),o;try{o=JSON.parse(n.stdout??"[]").find(l=>l.id===t)}catch{o=void 0}if(!o)return{installed:!1,scope:null,enabled:null,apiUrl:null,apiKeySet:!1};let r=tt(e,["plugin","configure",t,"--json"]),s=null,i=!1;try{let c=JSON.parse(r.stdout??"{}");s=c.inputs?.apiUrl?W(c.inputs.apiUrl):null,i=Array.isArray(c.configured)&&c.configured.includes("apiKey")}catch{s=null}return{installed:!0,scope:o.scope??null,enabled:o.enabled??null,apiUrl:s,apiKeySet:i}}function z(e,t){qs(Ks(e),{recursive:!0,mode:448});let n=`${e}.tmp`;js(n,`${JSON.stringify(t,null,2)}
35
- `,{mode:384}),Ns(n,384),Us(n,e)}function fo(e){if(!Ds(e))return null;try{return JSON.parse(Hs(e,"utf-8"))}catch{return null}}function go(e,t,n){let o=tt(e,["plugin","configure",t,"--values-stdin"],JSON.stringify({apiUrl:W(n.apiUrl),apiKey:n.apiKey}));return o.error?`could not run \`claude plugin configure\` (${o.error.message})`:o.status!==0?`\`claude plugin configure\` exited ${o.status}`:null}function Bt(e){let t=e.env??process.env,n=e.pluginId??_,o=W(e.target.apiUrl);if(!po(t))return{status:"failed",reason:"this Claude Code has no `claude plugin configure --values-stdin`; update it (`claude update`) or run `/plugin configure contextq` inside a session",state:null};let r=Q(t,n);if(!r.installed)return{status:"failed",reason:`${n} is not installed in this Claude Code profile`,state:r};let s=a(()=>new Date().toISOString(),"now"),i={version:1,pluginId:n,startedAt:s(),updatedAt:s(),step:"backed_up",before:{apiUrl:r.apiUrl,scope:r.scope,enabled:r.enabled},target:{apiUrl:o},previousKeySource:e.previousKeySource??null};z(e.journalPath,i);let c=go(t,n,e.target);if(c)return z(e.journalPath,{...i,step:"failed",updatedAt:s(),error:c}),{status:"failed",reason:c,state:Q(t,n)};z(e.journalPath,{...i,step:"configured",updatedAt:s()});let l=Q(t,n),d=[];if(l.apiUrl!==o&&d.push(`apiUrl is ${l.apiUrl??"unset"}, expected ${o}`),l.apiKeySet||d.push("apiKey is not set"),l.scope!==r.scope&&d.push(`scope changed from ${r.scope} to ${l.scope}`),l.enabled!==r.enabled&&d.push(`enabled changed from ${r.enabled} to ${l.enabled}`),d.length>0){let u=`the effective plugin config does not match the switch: ${d.join("; ")}`;return z(e.journalPath,{...i,step:"failed",updatedAt:s(),error:u}),{status:"failed",reason:u,state:l}}return z(e.journalPath,{...i,step:"verified",updatedAt:s()}),{status:r.apiUrl===o?"updated":"switched",state:l}}function Ms(e,t,n=process.env){let o=fo(e);if(!o)return{status:"failed",reason:`no plugin switch journal at ${e}`,state:null};if(o.before.apiUrl&&W(t.apiUrl)!==o.before.apiUrl)return{status:"failed",reason:`the restore endpoint ${W(t.apiUrl)} is not the one recorded before the switch (${o.before.apiUrl})`,state:null};let r=go(n,o.pluginId,t);if(r)return{status:"failed",reason:r,state:Q(n,o.pluginId)};let s=Q(n,o.pluginId);return s.apiUrl!==W(t.apiUrl)||!s.apiKeySet?{status:"failed",reason:`restore did not take effect (apiUrl is ${s.apiUrl??"unset"})`,state:s}:(z(e,{...o,step:"restored",updatedAt:new Date().toISOString()}),{status:"switched",state:s})}function Fs(e){if(!I.test(e.apiKey))return{written:!1,line:"[skip] platform credentials (a local token stays in the local state dir)"};let t=M({explicitKey:e.apiKey,ambientVarName:null,ambientKey:"",apiUrl:W(e.apiUrl)});return{written:t.action!=="skipped",line:t.credLine}}var nt=C(()=>{"use strict";me();G();a(Ls,"bin");a(W,"normalizeUrl");a(tt,"run");a(po,"supportsTerminalConfigure");a(Q,"readPluginState");a(z,"writeJournal");a(fo,"readJournal");a(go,"applyConfig");a(Bt,"reconfigurePlugin");a(Ms,"restorePlugin");a(Fs,"syncPlatformCredentials")});var _e={};de(_e,{CLOUD_MARKETPLACE_BASE:()=>Xt,appendBinding:()=>wo,gitRoot:()=>mo,readBinding:()=>ot,runLocalInit:()=>zs,writeLocalBinding:()=>yo});import{spawnSync as Js}from"child_process";import{existsSync as Bs,mkdirSync as Qs,readFileSync as ho,renameSync as Vs,writeFileSync as Xs}from"fs";import{homedir as Ys}from"os";import{basename as Gs,join as re,resolve as Vt}from"path";function mo(e){let t=Js("git",["-C",e,"rev-parse","--show-toplevel"],{encoding:"utf8"});if(t.status!==0)throw new Error(`${e} is not inside a git repository; run \`contextq init --local\` from the project repo`);let n=t.stdout.trim(),o=Vt(Ys());if(Vt(n)===o||Vt(n)===re(o,".claude"))throw new Error(`${n} is your home directory or ~/.claude, not a project repo; nothing was bound`);return n}function ot(e){let t=re(e,".contextq","config.json");if(!Bs(t))return null;try{return JSON.parse(ho(t,"utf8"))}catch{throw new Error(`${t} is not valid JSON; fix or delete it first`)}}function wo(e,t,n="contextq-cli"){let o=re(e,".contextq"),r=re(o,"config.json"),s=ot(e),i=s??{},c=i._entries??[],l=i.write_seq??c.length,d=!1;for(let[f,p]of t){if(i[f]===p)continue;let g=l;l+=1,c.push({key:f,value:p,written_by:n,based_on:g,seq:l,ts:new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}),i[f]=p,d=!0}if(!d)return"skipped";i.config_schema_version=Math.max(5,i.config_schema_version??5),i.write_seq=l,i._entries=c,Qs(o,{recursive:!0});let u=`${r}.tmp`;return Xs(u,`${JSON.stringify(i,null,2)}
36
- `),Vs(u,r),s?"updated":"created"}function yo(e,t){return wo(e,[["mode","local"],["workspace",t.workspace],["agent",t.agent]])}async function zs(e){let t=e.log??(v=>console.log(v)),n=e.env??process.env,o=mo(e.cwd),r=Gs(o),s=e.workspace?te(e.workspace.toLowerCase(),"--workspace"):B(r),i=e.agent?te(e.agent,"--agent"):B(r),c=e.runtime?.stateDir??U(n),l=await Se({...e.runtime,stateDir:c});l.warning&&t(`[warn] ${l.warning}`),t(`${l.started?"[start]":"[skip] "} local server at ${l.url} (edge ${l.edgeVersion})`);let d=ho(re(c,"token"),"utf8").trim(),u=l.url,f=Nt(n);if("reason"in f)t(`[info] Claude Code plugin not wired: ${f.reason}. Install Claude Code 2.1.281+ and run \`contextq init --local\` again.`);else{let v=Q(n);if(v.installed){let k=Bt({target:{apiUrl:u,apiKey:d},journalPath:re(c,"plugin-config.backup.json"),previousKeySource:v.apiUrl&&v.apiUrl.startsWith("http://127.0.0.1")?"local-token":"platform-cred",env:n});if(k.status==="failed")throw new Error(`could not point the installed ${_} at the local server: ${k.reason}`);t(`${k.status==="switched"?"[rotate]":"[skip] "} ${_} -> ${u} (${k.status})`)}else{let k=Qe(o,u,d,Xt,n,{marketplaceBase:e.marketplaceBase??Xt});if(k.status!=="installed")throw new Error(`could not install ${_}: ${k.status==="unavailable",k.reason}`);t(`[add] ${_} at project scope -> ${u}`)}}let p={authorization:`Bearer ${d}`},g=await fetch(`${u}/contexts?workspace=${encodeURIComponent(s)}&limit=1`,{headers:p}),h=await fetch(`${u}/hooks/session-start`,{method:"POST",headers:{...p,"content-type":"application/json"},body:JSON.stringify({agent:i,workspace:s})}),w=h.ok?(await h.json()).hookSpecificOutput?.additionalContext??"":"";if(!g.ok||!w.startsWith("ContextQ local mode"))throw new Error(`the local server did not answer a memory read and boot-lite (HTTP ${g.status}/${h.status}); run \`contextq local status\``);let x=(await g.json()).total??0;t(`[check] local memory answers: ${x} note(s) in ${s}, boot-lite in local mode`);let y=yo(o,{workspace:s,agent:i});t(`${y==="skipped"?"[skip] ":y==="created"?"[create]":"[add] "} .contextq/config.json (mode=local workspace=${s} agent=${i})`),t(""),t("Local mode is on for this repo: memory stays on this machine, no account needed. Open a new Claude Code session here to use it."),t("Board, handoffs and sessions need an account; `contextq attach` adds one later and moves your memory with you.")}var Xt,se=C(()=>{"use strict";Ce();me();nt();Xe();Xt="https://app.contextq.dev";a(mo,"gitRoot");a(ot,"readBinding");a(wo,"appendBinding");a(yo,"writeLocalBinding");a(zs,"runLocalInit")});var bo={};de(bo,{AttachError:()=>T,abortAttach:()=>ai,attachSwitch:()=>ii,attachTransfer:()=>ri,ensureLocalServer:()=>ut,fingerprint:()=>Gt,isSimulatedCrash:()=>si,journalPath:()=>Ae,reached:()=>Ee,readJournal:()=>dt,writeJournal:()=>P});import{createHash as Ws,randomUUID as Zs}from"crypto";import{chmodSync as ei,existsSync as st,mkdirSync as ti,readFileSync as Yt,renameSync as ni,rmSync as it,writeFileSync as xo}from"fs";import{dirname as oi,join as ie}from"path";function Ee(e,t){return ko.indexOf(e.state)>=ko.indexOf(t)}function Ae(e){return ie(e,"attach-journal.json")}function dt(e){let t=Ae(e);if(!st(t))return null;try{return JSON.parse(Yt(t,"utf8"))}catch{throw new T(`${t} is not readable JSON; move it aside and run \`contextq attach --restart\``)}}function P(e,t){let n=Ae(e);ti(oi(n),{recursive:!0,mode:448});let o=`${n}.tmp`;xo(o,`${JSON.stringify({...t,updatedAt:new Date().toISOString()},null,2)}
37
- `,{mode:384}),ei(o,384),ni(o,n)}function Gt(e){return Ws("sha256").update(e,"utf8").digest("hex").slice(0,12)}function Re(e){return e.replace(/\/+$/,"")}async function Pe(e,t,n,o,r){let s=await e(`${t.url}${o}`,{method:n,headers:{authorization:`Bearer ${t.token}`,...r?{"content-type":"application/json"}:{}},body:r?JSON.stringify(r):void 0}),i=await s.text();if(!s.ok)throw new T(`the local server answered HTTP ${s.status} for ${n} ${o}: ${i.slice(0,200)}`);return JSON.parse(i)}async function at(e,t,n,o,r,s){let i=await e(`${t}${r}`,{method:o,headers:{authorization:`Bearer ${n}`,...s?{"content-type":"application/json"}:{}},body:s?JSON.stringify(s):void 0}),c=await i.text();return{status:i.status,text:c}}async function ut(e){return{url:(await Se({...e.runtime,stateDir:e.stateDir})).url,token:Yt(ie(e.stateDir,"token"),"utf8").trim()}}function vo(e,t){let n=N();if(!n.apiKey||Re(n.apiUrl)!==e||Gt(n.apiKey)!==t.keyFingerprint)throw new T(`the attach in progress belongs to another login (its key is no longer the stored credential for ${e}); run \`contextq attach --restart\` to start over`);return n.apiKey}async function ri(e){let t=e.log??(l=>console.log(l)),n=e.fetchImpl??fetch,o=e.stateDir??U(),r=Re(e.cloudUrl),s=dt(o);if(s&&e.restart){if(Ee(s,"freezing"))throw new T("the attach already switched or is switching; finish it or run `contextq attach --abort` first");it(Ae(o),{force:!0}),s=null}if(s&&s.cloudUrl!==r)throw new T(`an attach to ${s.cloudUrl} is in progress; resume it with --url ${s.cloudUrl}, or run \`contextq attach --restart\``);s&&s.state==="login_pending"&&(s=null);let i=await ut({stateDir:o,runtime:e.runtime}),c;if(s)c=vo(r,s),t(`[info] resuming attach ${s.attachId} at ${s.state}`);else{s={version:1,attachId:Zs(),cloudUrl:r,tenantId:null,keyFingerprint:null,state:"login_pending",workspaceMap:{},acked:{},denied:{},leaveDenied:!!e.leaveDenied,consent:{ping:null},startedAt:new Date().toISOString(),updatedAt:new Date().toISOString()},P(o,s);try{c=await e.login();let l=await at(n,r,c,"GET","/api/workspaces");if(l.status!==200)throw new T(`${r} did not accept the approved key (HTTP ${l.status}); nothing was changed`)}catch(l){throw it(Ae(o),{force:!0}),l}M({explicitKey:c,ambientVarName:null,ambientKey:"",apiUrl:r}),s={...s,state:"authenticated",keyFingerprint:Gt(c)},P(o,s),t(`[check] signed in to ${r}`)}if(e.leaveDenied&&(s.leaveDenied=!0),!Ee(s,"workspaces_mapped")){let{workspaces:l}=await Pe(n,i,"GET","/workspaces");for(let d of l){if(d.contexts===0||s.workspaceMap[d.slug])continue;let u="global";if(d.slug!=="global"){let f=await at(n,r,c,"POST","/api/workspaces/ensure",{name:d.slug,slug:d.slug});if(f.status!==200&&f.status!==201)throw new T(`could not ensure workspace ${d.slug} on ${r} (HTTP ${f.status}); run \`contextq attach\` again to resume`);let p=JSON.parse(f.text);if(typeof p.tenantId=="number"){if(s.tenantId!==null&&s.tenantId!==p.tenantId)throw new T(`the account at ${r} is not the one this attach started with; run \`contextq attach --restart\``);s.tenantId=p.tenantId}u=p.slug}await Pe(n,i,"PUT","/sync/workspace-map",{local:d.slug,cloud:u}),s.workspaceMap[d.slug]=u,P(o,s),t(`[map] ${d.slug} -> ${u}`)}s.state="workspaces_mapped",P(o,s)}if(!Ee(s,"transferred")||s.state==="transfer_blocked"){s.state="transferring",P(o,s);let l=Number.POSITIVE_INFINITY;for(;;){let d=await Pe(n,i,"POST","/sync/push",{parentUrl:r,apiKey:c,...e.batchMaxContexts?{batchMaxContexts:e.batchMaxContexts}:{}});for(let f of d.refs)f.outcome==="accepted"&&typeof f.cloudId=="number"?(s.acked[f.ref]=f.cloudId,delete s.denied[f.ref]):(f.outcome==="denied"||f.outcome==="too_large")&&(s.denied[f.ref]=f.reason??f.outcome);if(P(o,s),d.failures.length>0)throw new T(`the transfer stopped: ${d.failures[0].error}; run \`contextq attach\` again to resume (nothing is sent twice)`);if(d.pending-(s.leaveDenied?Object.keys(s.denied).length:0)<=0)break;if(d.pending>=l||Object.keys(s.denied).length>=d.pending){s.state="transfer_blocked",P(o,s);let f=Object.entries(s.denied).map(([p,g])=>` ${p}: ${g}`).join(`
38
- `);throw new T(`${d.pending} note(s) were refused by ${r}:
29
+ `;ls(n,s)}function Wn(e,t,n,o=D,r={}){let s=ve(e),i=Kt(t,n,"codex",o),c="Codex requires this hook to be TRUSTED before it runs -- start `codex` in this repo and approve it at the hook-trust prompt (or review/trust via `/hooks` in a session)",l=hs(e)?" -- WARNING: .codex/config.toml also declares an inline [hooks] table; Codex loads both hooks.json and the inline table and warns on every run. Prefer one representation per layer.":"",u=Gn({path:s,dir:X(e,".codex"),root:e,topLevelDefaults:{description:"ContextQ boot hook (managed by `npx contextq init` -- re-run the installer to update, do not hand-edit)"}},i,`SessionStart boot hook wired (agent=${t}, workspace=${n}). ${c}.${l}`,r.refresh===!0);return(u.action==="wired"||u.action==="updated")&&ms(e,".codex/hooks.json"),u}function Qn(e){return e.startsWith("'")&&e.endsWith("'")&&e.length>=2?e.slice(1,-1):e}function ws(e,t,n=D){let o=/--agent\s+('[^']*'|\S+)/.exec(e)?.[1],r=/--workspace\s+('[^']*'|\S+)/.exec(e)?.[1];if(!o||!r)return{stale:!0};let s=Kt(Qn(o),Qn(r),t,n);return s===e?{stale:!1}:{stale:!0,expected:s}}function Mt(e,t,n=D){if(!ie(e))return[];let o;try{o=JSON.parse(We(e,"utf-8"))}catch{return[]}let r=[],s=o?.hooks?.SessionStart;if(!Array.isArray(s))return r;for(let i of s)for(let c of i?.hooks??[]){if(!c||typeof c.command!="string"||!Vn.test(c.command))continue;let l=ws(c.command,t,n);l.stale&&r.push(`boot hook command is old: \`${R(c.command)}\`${l.expected?` (current: \`${R(l.expected)}\`)`:""}`)}return r}var Vn,D,Ze=S(()=>{"use strict";Xe();Vn=/contextq-boot\.sh|contextq.*boot/;a(Yn,"shellQuote");D="npx -y --prefer-offline @contextq/cli";a(Kt,"buildBootCommand");a(gs,"gitTracked");a(Gn,"mergeSessionStartHook");a(xe,"claudeHookPath");a(ve,"codexHookPath");a(zn,"wireSessionHook");a(hs,"hasInlineHooksTable");a(ms,"ensureGitignored");a(Wn,"wireCodexSessionHook");a(Qn,"unquoteSlug");a(ws,"bootCommandDrift");a(Mt,"staleBootCommands")});import{chmodSync as ys,existsSync as Xt,mkdirSync as to,readFileSync as ks,writeFileSync as no}from"fs";import{dirname as oo,join as et}from"path";function ro(e,t=D){return`${t} hook boot --format ${e} --require-config`}function Jt(e,t,n=D){return`${n} hook snapshot --format ${e} --reason ${t} --require-config`}function so(e){return e==="copilot"?[{event:"sessionEnd",reason:"end"},{event:"preCompact",reason:"compact"}]:[]}function be(e,t,n){let o=me(e,t,n);return e==="gemini"?et(o,"settings.json"):e==="cursor"?et(o,"hooks.json"):et(o,"hooks","contextq.json")}function io(e){if(!Xt(e))return{doc:{}};let t=ks(e,"utf-8");try{let n=t.trim()===""?{}:JSON.parse(t);return!n||typeof n!="object"||Array.isArray(n)?{error:"top level is not a JSON object"}:{doc:n}}catch(n){return{error:`not valid JSON (${n.message})`}}}function Zn(e,t){let n=e.hooks??={};if(typeof n!="object"||Array.isArray(n))return;let o=n[t]??=[];return Array.isArray(o)?o:void 0}function eo(e,t=Yt){return JSON.stringify(e).match(t)!==null}function ao(e){if(!e||typeof e!="object")return[];let t=e,n=[];for(let o of["command","bash"])typeof t[o]=="string"&&n.push({holder:t,field:o,value:t[o]});if(Array.isArray(t.hooks))for(let o of t.hooks)n.push(...ao(o));return n}function Bt(e,t,n){let o=[];for(let r of e)if(JSON.stringify(r).match(t))for(let s of ao(r))t.test(s.value)&&s.value!==n&&o.push(s);return o}function co(e,t,n=D){let{doc:o}=io(t),r=o?.hooks;if(!r||typeof r!="object"||Array.isArray(r))return[];let s=[{event:e==="gemini"?"SessionStart":"sessionStart",marker:Yt,expected:ro(e,n),what:"boot hook"},...so(e).map(({event:c,reason:l})=>({event:c,marker:Ft,expected:Jt(e,l,n),what:`${c} snapshot hook`}))],i=[];for(let{event:c,marker:l,expected:u,what:d}of s){let f=r[c];if(Array.isArray(f))for(let p of Bt(f,l,u))i.push(`${d} command is old: \`${R(p.value)}\` (current: \`${R(u)}\`)`)}return i}function lo(e,t){let n=be(e,t.homeDir,t.env),o=Xt(n),{doc:r,error:s}=io(n);if(!r)return{surface:e,path:n,action:"refused",detail:`${s}; left untouched`};let i=ro(e,t.runner),c=e==="gemini"?"SessionStart":"sessionStart";e!=="gemini"&&(r.version??=1);let l=Zn(r,c);if(!l)return{surface:e,path:n,action:"refused",detail:`hooks.${c} is not an array; left untouched`};let u=eo(l),d=Bt(l,Yt,i).map(m=>({slot:m,expected:i})),f=[];for(let{event:m,reason:k}of so(e)){let y=Zn(r,m);if(!y)return{surface:e,path:n,action:"refused",detail:`hooks.${m} is not an array; left untouched`};let v=Jt(e,k,t.runner);if(!eo(y,Ft))f.push({list:y,reason:k,event:m});else for(let x of Bt(y,Ft,v))d.push({slot:x,expected:v})}if(d.length>0&&t.refresh!==!0)return{surface:e,path:n,action:"stale",detail:`${d.map(({slot:m,expected:k})=>`command differs: old \`${R(m.value)}\` vs new \`${R(k)}\``).join("; ")}; run init --refresh to update`};if(u&&f.length===0&&d.length===0)return{surface:e,path:n,action:"skipped",detail:"contextq hooks up to date"};if(we(n))return{surface:e,path:n,action:"refused",detail:`sits in a git work tree and is not gitignored; add the SessionStart hook by hand: ${i}`};let p=d.map(({slot:m,expected:k})=>`old \`${R(m.value)}\` -> new \`${R(k)}\``);for(let{slot:m,expected:k}of d)m.holder[m.field]=k;u||(e==="gemini"?l.push({hooks:[{type:"command",name:"contextq-boot",command:i,timeout:3e4}]}):e==="cursor"?l.push({command:i,timeout:30}):l.push({type:"command",bash:i,timeoutSec:30}));for(let m of f)m.list.push({type:"command",bash:Jt(e,m.reason,t.runner),timeoutSec:30});to(oo(n),{recursive:!0}),no(n,`${JSON.stringify(r,null,2)}
30
+ `,{mode:384}),ys(n,384);let g=`SessionStart boot hook (runs only in repos with a .contextq/config.json); ${e==="cursor"?"Cursor runs sessionStart without waiting, so the first turn may not see it":"the boot pack is injected at session start"}`,h=f.length>0?`${f.map(m=>m.event).join(" + ")} snapshot hook (git state only, no transcript; respects capture_scope)`:"";return{surface:e,path:n,action:p.length>0?"updated":o?"added":"created",detail:[p.length>0?`commands replaced: ${p.join("; ")}`:"",u?"":g,h].filter(Boolean).join("; ")}}function uo(e,t,n){let o=et(e,".contextq","config.json");return Xt(o)?{path:o,action:"skipped",detail:"repo already bound -- left untouched"}:(to(oo(o),{recursive:!0}),no(o,`${JSON.stringify({config_schema_version:5,workspace:t,agent:n,read_scope:"own+global"},null,2)}
31
+ `),{path:o,action:"created",detail:`binds this repo to workspace ${t} for the host hooks; commit it so other machines share the binding`})}var Yt,Ft,Qt=S(()=>{"use strict";z();Xe();Ze();Yt=/contextq.*hook boot|contextq-boot\.sh/,Ft=/contextq.*hook snapshot/;a(ro,"hostHookCommand");a(Jt,"hostSnapshotCommand");a(so,"snapshotEvents");a(be,"hostHookPath");a(io,"readJsonObject");a(Zn,"eventList");a(eo,"alreadyWired");a(ao,"commandSlots");a(Bt,"staleSlots");a(co,"staleHostHookCommands");a(lo,"wireHostHook");a(uo,"ensureRepoConfig")});import{spawnSync as Vt}from"child_process";import{existsSync as xs,readFileSync as vs}from"fs";import{join as bs}from"path";function fo(e){return e[Gt]||"claude"}function $s(e){let t=/(\d+)\.(\d+)\.(\d+)/.exec(e);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function Ss(e,t){for(let n=0;n<3;n++)if(e[n]!==t[n])return e[n]>t[n];return!0}function go(e){let t=bs(e,".mcp.json");if(!xs(t))return!1;try{return!!JSON.parse(vs(t,"utf-8")).mcpServers?.contextq}catch{return!1}}function zt(e=process.env){let t=Vt(fo(e),["--version"],{encoding:"utf-8",env:e});if(t.error||t.status!==0)return{reason:"the `claude` binary was not found on PATH"};let n=$s(t.stdout);return n?Ss(n,po)?{version:n.join(".")}:{reason:`Claude Code ${n.join(".")} is older than ${po.join(".")}, the ContextQ plugin's floor (its function-hook module needs prompt.edit and ui.render AbovePrompt)`}:{reason:`could not read a version from \`claude --version\` (${t.stdout.trim()})`}}function Cs(e){let t=e.split(`
32
+ `).map(n=>n.trim()).filter(n=>n.startsWith("{"));for(let n=t.length-1;n>=0;n--)try{return JSON.parse(t[n])}catch{continue}return null}function Es(e){return`${e.replace(/\/$/,"")}/plugin/marketplace.json`}function tt(e,t,n,o,r=process.env,s={}){let i=zt(r);if("reason"in i)return{status:"unavailable",reason:i.reason};let c=fo(r),l=Vt(c,["plugin","marketplace","add",Es(s.marketplaceBase??t),"--scope","project"],{cwd:e,encoding:"utf-8",env:r});if(l.error||l.status!==0)return{status:"failed",reason:`\`claude plugin marketplace add\` exited ${l.status??"with an error"}`};let u=["plugin","install",E,"--scope","project","--json","--config",`apiKey=${n}`];t.replace(/\/$/,"")!==o.replace(/\/$/,"")&&u.push("--config",`apiUrl=${t}`);let d=Vt(c,u,{cwd:e,encoding:"utf-8",env:r}),f=Cs(d.stdout??"");if(!f)return{status:"failed",reason:`\`claude plugin install --json\` printed no result line (exit ${d.status??"error"})`};if(f.outcome!=="ok"||d.status!==0){let g=typeof f.failureCode=="string"?` [${f.failureCode}]`:"";return{status:"failed",reason:`${String(f.message??"install failed")}${g}`}}let p=String(f.message??"installed");return{status:"installed",message:p,alreadyInstalled:/already installed/i.test(p)}}function ho(e){return e.noPlugin?{delegate:!1,reason:"--no-plugin: wiring Claude Code through .mcp.json"}:e.legacyMcpEntry?{delegate:!1,reason:".mcp.json already wires contextq -- keeping that path, not installing the plugin on top of it (both would register the same MCP server)"}:e.verifyStatus===401||e.verifyStatus===403?{delegate:!1,reason:`the server rejected this key (HTTP ${e.verifyStatus}) -- not handing it to the plugin, whose apiKey is stored machine-wide and would replace a working one; wiring .mcp.json instead`}:{delegate:!0}}var E,po,Gt,$e=S(()=>{"use strict";E="contextq@contextq-plugins",po=[2,1,281],Gt="CONTEXTQ_CLAUDE_BIN";a(fo,"claudeBin");a($s,"parseClaudeVersion");a(Ss,"versionAtLeast");a(go,"hasLegacyMcpEntry");a(zt,"detectClaude");a(Cs,"lastJsonLine");a(Es,"marketplaceUrl");a(tt,"installPlugin");a(ho,"decidePluginDelegation")});function ae(e,t){if(!_s.test(e))throw new Error(`${t} value "${e}" is not a valid slug -- allowed: ${nt}`);return e}function Y(e){let t=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"");return t=t.slice(0,64),t=t.replace(/[-_]+$/g,""),t===""?"default":t}var _s,nt,ot=S(()=>{"use strict";_s=/^[a-z0-9][a-z0-9_-]{0,63}$/,nt='lowercase letters, digits, "-", "_" (must start with a letter/digit, max 64 chars)';a(ae,"validateSlug");a(Y,"sanitizeSlugFallback")});import{execFileSync as Ps}from"child_process";import{existsSync as mo,readFileSync as Ts}from"fs";import{basename as wo,join as yo}from"path";function Se(e){let t=(e??"").trim();return!t||/^\$\{[^}]*\}$/.test(t)?"":t}function Ee(e){let t=H(),n=Se(e.CONTEXT_API_KEY)||Se(e.CTQ_API_KEY),o=n||t.apiKey,r=(n?Se(e.CONTEXT_API_URL)||Se(e.CTQ_API_URL):"")||t.apiUrl||Se(e.CONTEXT_API_URL)||As;return{apiKey:o,apiUrl:r.replace(/\/+$/,"")}}function Wt(e,t){if(!t||t.trim()==="")return e;try{let n=JSON.parse(t);if(typeof n.cwd=="string"&&n.cwd)return n.cwd;if(Array.isArray(n.workspace_roots)&&typeof n.workspace_roots[0]=="string")return n.workspace_roots[0]}catch{return e}return e}function U(e,t){try{return Ps("git",["-C",e,...t],{stdio:["ignore","pipe","ignore"],encoding:"utf-8"}).trim()}catch{return""}}function Zt(e,t,n){let o=yo(e,".contextq","config.json");if(mo(o))return Ts(o,"utf-8");let r=Y(wo(e));return JSON.stringify({workspace:(n||r).toLowerCase(),agent:t||r,read_scope:"own+global"})}function rt(e,t){return JSON.stringify(e==="cursor"?{additional_context:t}:e==="copilot"?{additionalContext:t}:{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:t}})}async function ko(e){let t=Wt(e.cwd,e.stdinText),n=U(t,["rev-parse","--show-toplevel"]);if(!n||e.requireConfig&&!mo(yo(n,".contextq","config.json")))return"";let{apiKey:o,apiUrl:r}=Ee(e.env);if(!o)return rt(e.format,"ContextQ: no API key found (CONTEXT_API_KEY or the credentials file `npx @contextq/cli login` or `init` writes), so no memory was loaded this session. Fix: run npx @contextq/cli login once on this machine.");let s=new URLSearchParams({config:Zt(n,e.agent,e.workspace),agent_slug:e.agent??"",project:wo(n),git_root:n,branch:U(n,["branch","--show-current"]),head_sha:U(n,["rev-parse","--short","HEAD"]),goal:"",board:"",token_budget:"3000"}),i=e.fetchImpl??fetch,c=0,l="";try{let u=await i(`${r}/api/agent/hooks/session-start`,{method:"POST",headers:{Authorization:`Bearer ${o}`,Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:s.toString(),signal:AbortSignal.timeout(e.timeoutMs??1e4)});c=u.status,l=await u.text()}catch{c=0}if(c===401||c===403)return rt(e.format,`ContextQ: ${r} refused the stored API key (HTTP ${c}): it is expired, revoked or wrong, so no memory was loaded and nothing will be saved this session. Fix: npx @contextq/cli init --key <new sk_live_ key>, or run init from a terminal to be prompted.`);if(c===0||c>=500)return rt(e.format,`ContextQ: ${r} is unreachable or failing (HTTP ${c}), so no memory was loaded this session. The key was not checked; memory resumes when the server answers again.`);if(c<200||c>=300||l.trim()==="")return"";try{let d=JSON.parse(l).hookSpecificOutput?.additionalContext;return typeof d!="string"||d===""?"":rt(e.format,d)}catch{return""}}async function en(e=500){return process.stdin.isTTY?"":new Promise(t=>{let n="",o=a(()=>{process.stdin.removeAllListeners("data"),process.stdin.removeAllListeners("end"),process.stdin.pause(),t(n)},"done"),r=setTimeout(o,e);process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{n+=s}),process.stdin.on("end",()=>{clearTimeout(r),o()})})}var Ce,As,st=S(()=>{"use strict";W();ot();Ce=["claude","codex","gemini","cursor","copilot"],As="https://app.contextq.dev";a(Se,"usable");a(Ee,"resolveHookCreds");a(Wt,"sessionCwd");a(U,"git");a(Zt,"hookConfig");a(rt,"formatForHost");a(ko,"runHookBoot");a(en,"readStdinIfPiped")});var Lo={};he(Lo,{DEFAULT_LOCAL_PORT:()=>Do,EDGE_PACKAGE:()=>ln,EDGE_SOURCE_ENV:()=>qo,EDGE_VERSION_PIN:()=>No,LocalRuntimeError:()=>j,MIN_NODE_MAJOR:()=>_e,STATE_DIR_ENV:()=>an,checkNode:()=>ct,defaultLocalStateDir:()=>K,preflight:()=>Uo,provision:()=>jo,start:()=>Re,status:()=>de,stop:()=>ii});import{spawn as Vs,spawnSync as sn}from"child_process";import{chmodSync as Gs,closeSync as zs,existsSync as le,mkdirSync as Ws,openSync as Zs,readFileSync as at,rmSync as it,writeFileSync as Io}from"fs";import{homedir as ei,platform as ti}from"os";import{join as O}from"path";function K(e=process.env){if(e[an])return e[an];let t=ei();if(ti()==="darwin")return O(t,"Library","Application Support","contextq","local");let n=e.XDG_DATA_HOME&&e.XDG_DATA_HOME.trim()!==""?e.XDG_DATA_HOME:O(t,".local","share");return O(n,"contextq","local")}function Ae(e){let t=e.version??No,n=e.stateDir??K();return{stateDir:n,runtimeDir:e.runtimeDir??O(n,"runtime"),port:e.port??Do,version:t,source:e.packageSource??process.env[qo]??`${ln}@${t}`,nodeVersion:e.nodeVersion??process.versions.node,nodeBin:e.nodeBin??process.execPath,npmBin:e.npmBin??"npm",fetchImpl:e.fetchImpl??fetch,startTimeoutMs:e.startTimeoutMs??15e3}}function Ho(e){return O(e.runtimeDir,`edge-${e.version}`)}function Pe(e){return O(Ho(e),"node_modules","@contextq","edge")}function ct(e){let t=Number(e.split(".")[0]);if(!Number.isFinite(t)||t<_e)throw new j(`ContextQ local mode needs Node ${_e} or newer, and this is Node ${e}; install Node ${_e}+ and run \`contextq local start\` again.`)}function cn(e){Ws(e,{recursive:!0,mode:448}),Gs(e,448)}function Oo(e){let t=O(Pe(e),"package.json");if(!le(t))return null;try{return JSON.parse(at(t,"utf8")).version}catch{return null}}function jo(e={}){let t=Ae(e);ct(t.nodeVersion);let n=Ho(t);if(Oo(t)===t.version&&le(O(Pe(t),"dist","cli.js")))return{dir:Pe(t),installed:!1};cn(t.stateDir),cn(n),le(O(n,"package.json"))||Io(O(n,"package.json"),`${JSON.stringify({name:"contextq-local-runtime",private:!0},null,2)}
33
+ `);let o=sn(t.npmBin,["install","--no-audit","--no-fund","--omit=dev","--install-links","--prefix",n,t.source],{encoding:"utf8",timeout:3e5});if(o.error||o.status!==0){let s=`${o.stderr??""}`.trim().split(`
34
+ `).slice(-3).join(" | ");throw new j(`could not install ${t.source} into ${n}${/E404|No matching version|ETARGET/.test(s)?" (that version is not published yet)":""}: ${s||o.error?.message||`npm exited ${o.status}`}`)}let r=Oo(t);if(r!==t.version)throw new j(`installed ${ln} is ${r??"missing"}, but this CLI is pinned to ${t.version}; remove ${n} and run \`contextq local start\` again.`);return{dir:Pe(t),installed:!0}}function Uo(e={}){let t=Ae(e);ct(t.nodeVersion);let n=Pe(t),o=sn(t.nodeBin,["--input-type=module","-e","import Database from 'better-sqlite3'; new Database(':memory:').prepare('select 1').get();"],{cwd:n,encoding:"utf8",timeout:3e4});if(o.status!==0){let s=`${o.stderr??""}`.trim().split(`
35
+ `).find(i=>/Error|error/.test(i))??`exit ${o.status}`;throw new j(`better-sqlite3 does not load on this machine (${s.slice(0,200)}); local mode supports macOS arm64 and Linux x64 with Node ${_e}+ and npm prebuilt binaries.`)}return sn(t.nodeBin,["--input-type=module","-e","import { createRequire } from 'node:module'; import Database from 'better-sqlite3'; createRequire(import.meta.url + '/../')('sqlite-vec').load(new Database(':memory:'));"],{cwd:n,encoding:"utf8",timeout:3e4}).status!==0?{vectorSearch:!1,warning:"sqlite-vec does not load here; local search runs keyword-only (FTS5)."}:{vectorSearch:!0}}function Te(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}function ni(e){let t=L(e).pid;if(!le(t))return null;let n=Number(at(t,"utf8").trim());return Number.isInteger(n)&&n>0?n:null}function oi(e){let t=L(e).endpoint;if(!le(t))return null;try{return JSON.parse(at(t,"utf8"))}catch{return null}}async function ri(e,t){try{let n=await e(`${t}/health`,{signal:AbortSignal.timeout(2e3)});return n.ok?await n.json():null}catch{return null}}async function de(e={}){let t=Ae(e),n={running:!1,owned:!1,url:null,pid:null,edgeVersion:null,pinnedVersion:t.version,stateDir:t.stateDir},o=ni(t.stateDir),r=oi(t.stateDir);if(o===null)return{...n,note:"not started"};if(!Te(o))return it(L(t.stateDir).pid,{force:!0}),{...n,note:`not running (removed a stale pidfile for pid ${o})`};if(!r||r.pid!==o)return{...n,pid:o,note:`pid ${o} is alive but has not published its endpoint`};let s=await ri(t.fetchImpl,r.url);return!s||s.mode!=="local"||s.service!=="contextq-edge"?{...n,pid:o,url:r.url,note:`pid ${o} does not answer as a ContextQ local server at ${r.url}`}:{...n,running:!0,owned:!0,url:r.url,pid:o,edgeVersion:String(s.edgeVersion??"")||null}}async function si(e){let t=`http://127.0.0.1:${e.port}`;try{let n=await e.fetchImpl(`${t}/health`,{signal:AbortSignal.timeout(1500)});return(await n.json().catch(()=>null))?.service==="contextq-edge"?`another ContextQ local server (not started from ${e.stateDir})`:`a process that is not ContextQ (HTTP ${n.status})`}catch(n){return n.cause?.code==="ECONNREFUSED"?null:"a process that does not answer HTTP"}}async function Re(e={}){let t=Ae(e);ct(t.nodeVersion);let n=await de(e);if(n.running){if(n.edgeVersion&&n.edgeVersion!==t.version)throw new j(`the running local server is edge ${n.edgeVersion}, but this CLI is pinned to ${t.version}; run \`contextq local stop\` and start it again.`);return{...n,started:!1}}let o=await si(t);if(o)throw new j(`port ${t.port} on 127.0.0.1 is held by ${o}; it was left alone. Stop it or pass --port <n>.`);let{dir:r}=jo(e),s=Uo(e);cn(t.stateDir),it(L(t.stateDir).endpoint,{force:!0});let i=Zs(L(t.stateDir).log,"a",384),c=Vs(t.nodeBin,[O(r,"dist","cli.js"),"serve","--state-dir",t.stateDir,"--port",String(t.port)],{detached:!0,stdio:["ignore",i,i]});if(zs(i),c.pid===void 0)throw new j("could not start the local server process");Io(L(t.stateDir).pid,`${c.pid}
36
+ `,{mode:384}),c.unref();let l=Date.now()+t.startTimeoutMs;for(;Date.now()<l;){let d=await de(e);if(d.running)return{...d,started:!0,...s.warning?{warning:s.warning}:{}};if(!Te(c.pid))break;await new Promise(f=>setTimeout(f,200))}let u=le(L(t.stateDir).log)?at(L(t.stateDir).log,"utf8").trim().split(`
37
+ `).slice(-3).join(" | "):"";throw Te(c.pid)&&process.kill(c.pid,"SIGTERM"),it(L(t.stateDir).pid,{force:!0}),new j(`the local server did not come up within ${Math.round(t.startTimeoutMs/1e3)} s${u?`: ${u}`:""}`)}async function ii(e={}){let t=Ae(e),n=await de(e);if(!n.running||!n.owned||n.pid===null)return{stopped:!1,note:n.note??"not running"};process.kill(n.pid,"SIGTERM");let o=Date.now()+1e4;for(;Date.now()<o&&Te(n.pid);)await new Promise(r=>setTimeout(r,100));return Te(n.pid)?{stopped:!1,note:`pid ${n.pid} did not exit after SIGTERM; it was left running`}:(it(L(t.stateDir).pid,{force:!0}),{stopped:!0,note:`stopped pid ${n.pid}`})}var ln,No,Do,_e,an,qo,j,L,Oe=S(()=>{"use strict";ln="@contextq/edge",No="0.2.0",Do=47321,_e=22,an="CONTEXTQ_LOCAL_STATE_DIR",qo="CONTEXTQ_EDGE_PACKAGE",j=class extends Error{static{a(this,"LocalRuntimeError")}constructor(t){super(t),this.name="LocalRuntimeError"}};a(K,"defaultLocalStateDir");a(Ae,"resolve");L=a(e=>({pid:O(e,"edge.pid"),endpoint:O(e,"endpoint.json"),log:O(e,"edge.log")}),"paths");a(Ho,"runtimeRoot");a(Pe,"edgePackageDir");a(ct,"checkNode");a(cn,"ensureDir");a(Oo,"installedVersion");a(jo,"provision");a(Uo,"preflight");a(Te,"pidAlive");a(ni,"readPid");a(oi,"readEndpoint");a(ri,"health");a(de,"status");a(si,"portHolder");a(Re,"start");a(ii,"stop")});var un={};he(un,{readJournal:()=>Mo,readPluginState:()=>Q,reconfigurePlugin:()=>dn,restorePlugin:()=>mi,supportsTerminalConfigure:()=>Ko,syncPlatformCredentials:()=>wi,writeJournal:()=>Z});import{spawnSync as ai}from"child_process";import{chmodSync as ci,existsSync as li,mkdirSync as di,readFileSync as ui,renameSync as pi,writeFileSync as fi}from"fs";import{dirname as gi}from"path";function hi(e){return e[Gt]||"claude"}function ee(e){return e.replace(/\/+$/,"")}function lt(e,t,n){return ai(hi(e),t,{encoding:"utf-8",env:e,input:n,timeout:6e4})}function Ko(e=process.env){let t=lt(e,["plugin","configure","--help"]);return!t.error&&t.status===0&&/--values-stdin/.test(t.stdout??"")}function Q(e=process.env,t=E){let n=lt(e,["plugin","list","--json"]),o;try{o=JSON.parse(n.stdout??"[]").find(l=>l.id===t)}catch{o=void 0}if(!o)return{installed:!1,scope:null,enabled:null,apiUrl:null,apiKeySet:!1};let r=lt(e,["plugin","configure",t,"--json"]),s=null,i=!1;try{let c=JSON.parse(r.stdout??"{}");s=c.inputs?.apiUrl?ee(c.inputs.apiUrl):null,i=Array.isArray(c.configured)&&c.configured.includes("apiKey")}catch{s=null}return{installed:!0,scope:o.scope??null,enabled:o.enabled??null,apiUrl:s,apiKeySet:i}}function Z(e,t){di(gi(e),{recursive:!0,mode:448});let n=`${e}.tmp`;fi(n,`${JSON.stringify(t,null,2)}
38
+ `,{mode:384}),ci(n,384),pi(n,e)}function Mo(e){if(!li(e))return null;try{return JSON.parse(ui(e,"utf-8"))}catch{return null}}function Fo(e,t,n){let o=lt(e,["plugin","configure",t,"--values-stdin"],JSON.stringify({apiUrl:ee(n.apiUrl),apiKey:n.apiKey}));return o.error?`could not run \`claude plugin configure\` (${o.error.message})`:o.status!==0?`\`claude plugin configure\` exited ${o.status}`:null}function dn(e){let t=e.env??process.env,n=e.pluginId??E,o=ee(e.target.apiUrl);if(!Ko(t))return{status:"failed",reason:"this Claude Code has no `claude plugin configure --values-stdin`; update it (`claude update`) or run `/plugin configure contextq` inside a session",state:null};let r=Q(t,n);if(!r.installed)return{status:"failed",reason:`${n} is not installed in this Claude Code profile`,state:r};let s=a(()=>new Date().toISOString(),"now"),i={version:1,pluginId:n,startedAt:s(),updatedAt:s(),step:"backed_up",before:{apiUrl:r.apiUrl,scope:r.scope,enabled:r.enabled},target:{apiUrl:o},previousKeySource:e.previousKeySource??null};Z(e.journalPath,i);let c=Fo(t,n,e.target);if(c)return Z(e.journalPath,{...i,step:"failed",updatedAt:s(),error:c}),{status:"failed",reason:c,state:Q(t,n)};Z(e.journalPath,{...i,step:"configured",updatedAt:s()});let l=Q(t,n),u=[];if(l.apiUrl!==o&&u.push(`apiUrl is ${l.apiUrl??"unset"}, expected ${o}`),l.apiKeySet||u.push("apiKey is not set"),l.scope!==r.scope&&u.push(`scope changed from ${r.scope} to ${l.scope}`),l.enabled!==r.enabled&&u.push(`enabled changed from ${r.enabled} to ${l.enabled}`),u.length>0){let d=`the effective plugin config does not match the switch: ${u.join("; ")}`;return Z(e.journalPath,{...i,step:"failed",updatedAt:s(),error:d}),{status:"failed",reason:d,state:l}}return Z(e.journalPath,{...i,step:"verified",updatedAt:s()}),{status:r.apiUrl===o?"updated":"switched",state:l}}function mi(e,t,n=process.env){let o=Mo(e);if(!o)return{status:"failed",reason:`no plugin switch journal at ${e}`,state:null};if(o.before.apiUrl&&ee(t.apiUrl)!==o.before.apiUrl)return{status:"failed",reason:`the restore endpoint ${ee(t.apiUrl)} is not the one recorded before the switch (${o.before.apiUrl})`,state:null};let r=Fo(n,o.pluginId,t);if(r)return{status:"failed",reason:r,state:Q(n,o.pluginId)};let s=Q(n,o.pluginId);return s.apiUrl!==ee(t.apiUrl)||!s.apiKeySet?{status:"failed",reason:`restore did not take effect (apiUrl is ${s.apiUrl??"unset"})`,state:s}:(Z(e,{...o,step:"restored",updatedAt:new Date().toISOString()}),{status:"switched",state:s})}function wi(e){if(!N.test(e.apiKey))return{written:!1,line:"[skip] platform credentials (a local token stays in the local state dir)"};let t=B({explicitKey:e.apiKey,ambientVarName:null,ambientKey:"",apiUrl:ee(e.apiUrl)});return{written:t.action!=="skipped",line:t.credLine}}var dt=S(()=>{"use strict";$e();W();a(hi,"bin");a(ee,"normalizeUrl");a(lt,"run");a(Ko,"supportsTerminalConfigure");a(Q,"readPluginState");a(Z,"writeJournal");a(Mo,"readJournal");a(Fo,"applyConfig");a(dn,"reconfigurePlugin");a(mi,"restorePlugin");a(wi,"syncPlatformCredentials")});var Ie={};he(Ie,{CLOUD_MARKETPLACE_BASE:()=>fn,appendBinding:()=>Xo,gitRoot:()=>Bo,readBinding:()=>ut,runLocalInit:()=>Ci,writeLocalBinding:()=>Yo});import{spawnSync as yi}from"child_process";import{existsSync as ki,mkdirSync as xi,readFileSync as Jo,renameSync as vi,writeFileSync as bi}from"fs";import{homedir as $i}from"os";import{basename as Si,join as ue,resolve as pn}from"path";function Bo(e){let t=yi("git",["-C",e,"rev-parse","--show-toplevel"],{encoding:"utf8"});if(t.status!==0)throw new Error(`${e} is not inside a git repository; run \`contextq init --local\` from the project repo`);let n=t.stdout.trim(),o=pn($i());if(pn(n)===o||pn(n)===ue(o,".claude"))throw new Error(`${n} is your home directory or ~/.claude, not a project repo; nothing was bound`);return n}function ut(e){let t=ue(e,".contextq","config.json");if(!ki(t))return null;try{return JSON.parse(Jo(t,"utf8"))}catch{throw new Error(`${t} is not valid JSON; fix or delete it first`)}}function Xo(e,t,n="contextq-cli"){let o=ue(e,".contextq"),r=ue(o,"config.json"),s=ut(e),i=s??{},c=i._entries??[],l=i.write_seq??c.length,u=!1;for(let[f,p]of t){if(i[f]===p)continue;let g=l;l+=1,c.push({key:f,value:p,written_by:n,based_on:g,seq:l,ts:new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}),i[f]=p,u=!0}if(!u)return"skipped";i.config_schema_version=Math.max(5,i.config_schema_version??5),i.write_seq=l,i._entries=c,xi(o,{recursive:!0});let d=`${r}.tmp`;return bi(d,`${JSON.stringify(i,null,2)}
39
+ `),vi(d,r),s?"updated":"created"}function Yo(e,t){return Xo(e,[["mode","local"],["workspace",t.workspace],["agent",t.agent]])}async function Ci(e){let t=e.log??(v=>console.log(v)),n=e.env??process.env,o=Bo(e.cwd),r=Si(o),s=e.workspace?ae(e.workspace.toLowerCase(),"--workspace"):Y(r),i=e.agent?ae(e.agent,"--agent"):Y(r),c=e.runtime?.stateDir??K(n),l=await Re({...e.runtime,stateDir:c});l.warning&&t(`[warn] ${l.warning}`),t(`${l.started?"[start]":"[skip] "} local server at ${l.url} (edge ${l.edgeVersion})`);let u=Jo(ue(c,"token"),"utf8").trim(),d=l.url,f=zt(n);if("reason"in f)t(`[info] Claude Code plugin not wired: ${f.reason}. Install Claude Code 2.1.281+ and run \`contextq init --local\` again.`);else{let v=Q(n);if(v.installed){let x=dn({target:{apiUrl:d,apiKey:u},journalPath:ue(c,"plugin-config.backup.json"),previousKeySource:v.apiUrl&&v.apiUrl.startsWith("http://127.0.0.1")?"local-token":"platform-cred",env:n});if(x.status==="failed")throw new Error(`could not point the installed ${E} at the local server: ${x.reason}`);t(`${x.status==="switched"?"[rotate]":"[skip] "} ${E} -> ${d} (${x.status})`)}else{let x=tt(o,d,u,fn,n,{marketplaceBase:e.marketplaceBase??fn});if(x.status!=="installed")throw new Error(`could not install ${E}: ${x.status==="unavailable",x.reason}`);t(`[add] ${E} at project scope -> ${d}`)}}let p={authorization:`Bearer ${u}`},g=await fetch(`${d}/contexts?workspace=${encodeURIComponent(s)}&limit=1`,{headers:p}),h=await fetch(`${d}/hooks/session-start`,{method:"POST",headers:{...p,"content-type":"application/json"},body:JSON.stringify({agent:i,workspace:s})}),m=h.ok?(await h.json()).hookSpecificOutput?.additionalContext??"":"";if(!g.ok||!m.startsWith("ContextQ local mode"))throw new Error(`the local server did not answer a memory read and boot-lite (HTTP ${g.status}/${h.status}); run \`contextq local status\``);let k=(await g.json()).total??0;t(`[check] local memory answers: ${k} note(s) in ${s}, boot-lite in local mode`);let y=Yo(o,{workspace:s,agent:i});t(`${y==="skipped"?"[skip] ":y==="created"?"[create]":"[add] "} .contextq/config.json (mode=local workspace=${s} agent=${i})`),t(""),t("Local mode is on for this repo: memory stays on this machine, no account needed. Open a new Claude Code session here to use it."),t("Board, handoffs and sessions need an account; `contextq attach` adds one later and moves your memory with you.")}var fn,pe=S(()=>{"use strict";Oe();$e();dt();ot();fn="https://app.contextq.dev";a(Bo,"gitRoot");a(ut,"readBinding");a(Xo,"appendBinding");a(Yo,"writeLocalBinding");a(Ci,"runLocalInit")});var zo={};he(zo,{AttachError:()=>T,abortAttach:()=>Di,attachSwitch:()=>Ni,attachTransfer:()=>Oi,ensureLocalServer:()=>kt,fingerprint:()=>hn,isSimulatedCrash:()=>Ii,journalPath:()=>He,reached:()=>Ne,readJournal:()=>yt,writeJournal:()=>P});import{createHash as Ei,randomUUID as _i}from"crypto";import{chmodSync as Pi,existsSync as ft,mkdirSync as Ti,readFileSync as gn,renameSync as Ai,rmSync as gt,writeFileSync as Vo}from"fs";import{dirname as Ri,join as fe}from"path";function Ne(e,t){return Qo.indexOf(e.state)>=Qo.indexOf(t)}function He(e){return fe(e,"attach-journal.json")}function yt(e){let t=He(e);if(!ft(t))return null;try{return JSON.parse(gn(t,"utf8"))}catch{throw new T(`${t} is not readable JSON; move it aside and run \`contextq attach --restart\``)}}function P(e,t){let n=He(e);Ti(Ri(n),{recursive:!0,mode:448});let o=`${n}.tmp`;Vo(o,`${JSON.stringify({...t,updatedAt:new Date().toISOString()},null,2)}
40
+ `,{mode:384}),Pi(o,384),Ai(o,n)}function hn(e){return Ei("sha256").update(e,"utf8").digest("hex").slice(0,12)}function je(e){return e.replace(/\/+$/,"")}async function De(e,t,n,o,r){let s=await e(`${t.url}${o}`,{method:n,headers:{authorization:`Bearer ${t.token}`,...r?{"content-type":"application/json"}:{}},body:r?JSON.stringify(r):void 0}),i=await s.text();if(!s.ok)throw new T(`the local server answered HTTP ${s.status} for ${n} ${o}: ${i.slice(0,200)}`);return JSON.parse(i)}async function ht(e,t,n,o,r,s){let i=await e(`${t}${r}`,{method:o,headers:{authorization:`Bearer ${n}`,...s?{"content-type":"application/json"}:{}},body:s?JSON.stringify(s):void 0}),c=await i.text();return{status:i.status,text:c}}async function kt(e){return{url:(await Re({...e.runtime,stateDir:e.stateDir})).url,token:gn(fe(e.stateDir,"token"),"utf8").trim()}}function Go(e,t){let n=H();if(!n.apiKey||je(n.apiUrl)!==e||hn(n.apiKey)!==t.keyFingerprint)throw new T(`the attach in progress belongs to another login (its key is no longer the stored credential for ${e}); run \`contextq attach --restart\` to start over`);return n.apiKey}async function Oi(e){let t=e.log??(l=>console.log(l)),n=e.fetchImpl??fetch,o=e.stateDir??K(),r=je(e.cloudUrl),s=yt(o);if(s&&e.restart){if(Ne(s,"freezing"))throw new T("the attach already switched or is switching; finish it or run `contextq attach --abort` first");gt(He(o),{force:!0}),s=null}if(s&&s.cloudUrl!==r)throw new T(`an attach to ${s.cloudUrl} is in progress; resume it with --url ${s.cloudUrl}, or run \`contextq attach --restart\``);s&&s.state==="login_pending"&&(s=null);let i=await kt({stateDir:o,runtime:e.runtime}),c;if(s)c=Go(r,s),t(`[info] resuming attach ${s.attachId} at ${s.state}`);else{s={version:1,attachId:_i(),cloudUrl:r,tenantId:null,keyFingerprint:null,state:"login_pending",workspaceMap:{},acked:{},denied:{},leaveDenied:!!e.leaveDenied,consent:{ping:null},startedAt:new Date().toISOString(),updatedAt:new Date().toISOString()},P(o,s);try{c=await e.login();let l=await ht(n,r,c,"GET","/api/workspaces");if(l.status!==200)throw new T(`${r} did not accept the approved key (HTTP ${l.status}); nothing was changed`)}catch(l){throw gt(He(o),{force:!0}),l}B({explicitKey:c,ambientVarName:null,ambientKey:"",apiUrl:r}),s={...s,state:"authenticated",keyFingerprint:hn(c)},P(o,s),t(`[check] signed in to ${r}`)}if(e.leaveDenied&&(s.leaveDenied=!0),!Ne(s,"workspaces_mapped")){let{workspaces:l}=await De(n,i,"GET","/workspaces");for(let u of l){if(u.contexts===0||s.workspaceMap[u.slug])continue;let d="global";if(u.slug!=="global"){let f=await ht(n,r,c,"POST","/api/workspaces/ensure",{name:u.slug,slug:u.slug});if(f.status!==200&&f.status!==201)throw new T(`could not ensure workspace ${u.slug} on ${r} (HTTP ${f.status}); run \`contextq attach\` again to resume`);let p=JSON.parse(f.text);if(typeof p.tenantId=="number"){if(s.tenantId!==null&&s.tenantId!==p.tenantId)throw new T(`the account at ${r} is not the one this attach started with; run \`contextq attach --restart\``);s.tenantId=p.tenantId}d=p.slug}await De(n,i,"PUT","/sync/workspace-map",{local:u.slug,cloud:d}),s.workspaceMap[u.slug]=d,P(o,s),t(`[map] ${u.slug} -> ${d}`)}s.state="workspaces_mapped",P(o,s)}if(!Ne(s,"transferred")||s.state==="transfer_blocked"){s.state="transferring",P(o,s);let l=Number.POSITIVE_INFINITY;for(;;){let u=await De(n,i,"POST","/sync/push",{parentUrl:r,apiKey:c,...e.batchMaxContexts?{batchMaxContexts:e.batchMaxContexts}:{}});for(let f of u.refs)f.outcome==="accepted"&&typeof f.cloudId=="number"?(s.acked[f.ref]=f.cloudId,delete s.denied[f.ref]):(f.outcome==="denied"||f.outcome==="too_large")&&(s.denied[f.ref]=f.reason??f.outcome);if(P(o,s),u.failures.length>0)throw new T(`the transfer stopped: ${u.failures[0].error}; run \`contextq attach\` again to resume (nothing is sent twice)`);if(u.pending-(s.leaveDenied?Object.keys(s.denied).length:0)<=0)break;if(u.pending>=l||Object.keys(s.denied).length>=u.pending){s.state="transfer_blocked",P(o,s);let f=Object.entries(s.denied).map(([p,g])=>` ${p}: ${g}`).join(`
41
+ `);throw new T(`${u.pending} note(s) were refused by ${r}:
39
42
  ${f}
40
- Edit or delete them locally and run \`contextq attach\` again, or pass --leave-denied to keep them local-only`)}l=d.pending}s.state="transferred",P(o,s),t(`[push] ${Object.keys(s.acked).length} note(s) are in ${r}`)}return s}function Te(e){return ie(e,"attach-binding-before.json")}function si(e){return e instanceof ct}async function lt(e,t,n,o){await Pe(e,t,"PUT","/sync/write-lock",{lock:n,...o?{attachedTo:o}:{}})}async function rt(e,t,n,o,r){let{restorePlugin:s,readPluginState:i}=await Promise.resolve().then(()=>(nt(),Qt)),{appendBinding:c}=await Promise.resolve().then(()=>(se(),_e)),l=e.fetchImpl??fetch,d=e.env??process.env,u=[],f=ie(o,"plugin-config.backup.json"),p=i(d);if(p.installed&&p.apiUrl&&Re(p.apiUrl)!==Re(n.url)&&st(f)){let g=s(f,{apiUrl:n.url,apiKey:n.token},d);u.push(g.status==="failed"?`plugin NOT restored: ${g.reason}`:"plugin points at the local server again")}if(e.repoRoot&&st(Te(o))){let g=JSON.parse(Yt(Te(o),"utf8")),h=[];g.mode&&h.push(["mode",g.mode]),g.workspace&&h.push(["workspace",g.workspace]),h.length>0&&c(e.repoRoot,h),u.push("repo binding restored")}throw await lt(l,n,null),t.state="transferred",P(o,t),new T(`${r}; rolled back (${[...u,"local writes unlocked"].join(", ")}). Run \`contextq attach\` again to retry.`)}async function ii(e){let t=e.log??(h=>console.log(h)),n=e.fetchImpl??fetch,o=e.env??process.env,r=e.stateDir??U(),s=Re(e.cloudUrl),{reconfigurePlugin:i,readPluginState:c}=await Promise.resolve().then(()=>(nt(),Qt)),{appendBinding:l,readBinding:d}=await Promise.resolve().then(()=>(se(),_e)),u=dt(r);if(!u||!Ee(u,"transferred")||u.state==="transfer_blocked")throw new T("nothing is ready to switch: run the transfer first (`contextq attach`)");if(u.state==="attached")return u;let f=await ut({stateDir:r,runtime:e.runtime}),p=vo(s,u),g=a(h=>{if(e.crashAfter===h)throw new ct(`simulated crash after ${h}`)},"crash");if(u.state==="switching"&&(t("[info] an earlier attach stopped during the switch; redoing the switch (every step is idempotent)"),u.state="freezing",P(r,u)),u.state==="transferred"||u.state==="freezing"){u.consent={ping:e.pingConsent??u.consent.ping??!1},u.state="freezing",P(r,u),await lt(n,f,"attach"),g("freezing");let h=await Pe(n,f,"POST","/sync/push",{parentUrl:s,apiKey:p});for(let y of h.refs)y.outcome==="accepted"&&typeof y.cloudId=="number"&&(u.acked[y.ref]=y.cloudId);let w=u.leaveDenied?Object.keys(u.denied).length:0;if((h.failures.length>0||h.pending>w)&&await rt(e,u,f,r,`the final flush left ${h.pending} note(s) pending${h.failures[0]?` (${h.failures[0].error})`:""}`),u.state="switching",P(r,u),e.repoRoot&&!st(Te(r))){let y=d(e.repoRoot),v={mode:y?.mode??null,workspace:y?.workspace??null};xo(Te(r),`${JSON.stringify(v)}
41
- `,{mode:384})}if(c(o).installed){let y=i({target:{apiUrl:s,apiKey:p},journalPath:ie(r,"plugin-config.backup.json"),previousKeySource:"local-token",env:o});y.status==="failed"&&await rt(e,u,f,r,`switching the plugin failed: ${y.reason}`),t(`[rotate] plugin -> ${s}`)}else t("[info] no installed ContextQ plugin in this Claude Code profile; only the repo binding switches");if(g("switching"),e.repoRoot){let v=d(e.repoRoot)?.workspace??"global",k=u.workspaceMap[v]??v;l(e.repoRoot,[["mode","cloud"],["workspace",k]]);for(let m of["bound-host","local-ids.json"])it(ie(e.repoRoot,".contextq","cache",m),{force:!0});t(`[bind] .contextq/config.json -> mode=cloud workspace=${k}`)}u.state="verifying",P(r,u)}if(u.state==="verifying"){let h=e.repoRoot?d(e.repoRoot)?.workspace??"global":"global",w=await at(n,s,p,"POST","/api/agent/boot",{agent:"contextq-attach",workspace:h}),x=Object.values(u.acked)[0],y=x===void 0?{status:200,text:""}:await at(n,s,p,"GET",`/api/contexts/${x}`),v=w.status===200&&/"mode"\s*:\s*"local"/.test(w.text);(w.status!==200||v||y.status!==200)&&await rt(e,u,f,r,`the cloud did not verify (boot HTTP ${w.status}${v?", still local":""}, read HTTP ${y.status})`),await lt(n,f,"attached",s),u.state="attached",P(r,u),it(Te(r),{force:!0}),t(`[check] ${s} answers boot and reads an imported note`)}return u}async function ai(e){let t=e.stateDir??U(),n=dt(t);if(!n)return"no attach in progress";if(n.state==="attached")return"the attach already finished; nothing to abort";let o=await ut({stateDir:t,runtime:e.runtime});if(["freezing","switching","verifying"].includes(n.state))try{await rt(e,n,o,t,"aborted")}catch(r){return r.message}return await lt(e.fetchImpl??fetch,o,null),`attach left at ${n.state}; local writes are unlocked`}var T,ko,ct,$o=C(()=>{"use strict";Ce();G();T=class extends Error{static{a(this,"AttachError")}constructor(t){super(t),this.name="AttachError"}},ko=["login_pending","authenticated","workspaces_mapped","transferring","transfer_blocked","transferred","freezing","switching","verifying","attached"];a(Ee,"reached");a(Ae,"journalPath");a(dt,"readJournal");a(P,"writeJournal");a(Gt,"fingerprint");a(Re,"normalizeUrl");a(Pe,"localJson");a(at,"cloudCall");a(ut,"ensureLocalServer");a(vo,"storedKeyFor");a(ri,"attachTransfer");a(Te,"bindingFile");ct=class extends Error{static{a(this,"SimulatedCrash")}};a(si,"isSimulatedCrash");a(lt,"setLock");a(rt,"rollback");a(ii,"attachSwitch");a(ai,"abortAttach")});var Co={};de(Co,{formatHuman:()=>bi,runDoctor:()=>vi});import{randomUUID as ci}from"crypto";import{existsSync as li,readFileSync as di}from"fs";import{homedir as ui}from"os";import{join as Wt}from"path";import{parse as pi}from"smol-toml";function j(e){try{return di(e,"utf-8")}catch{return null}}function pt(e){let t=e.startsWith("sk_live_")?"sk_live_":e.startsWith("ctq_local_")?"ctq_local_":null;return t?`prefix="${t}" length=${e.length}`:`unrecognized format length=${e.length}`}async function Z(e,t,n,o){let r;try{r=await fetch(t,{method:e,headers:{...n?{Authorization:`Bearer ${n}`}:{},...o!==void 0?{"content-type":"application/json"}:{}},...o!==void 0?{body:JSON.stringify(o)}:{},signal:AbortSignal.timeout(fi)})}catch(s){throw new Error(`could not reach ${t} (${s.message})`)}return{status:r.status,text:await r.text()}}function ce(e){try{return e.trim()===""?null:JSON.parse(e)}catch{return null}}function So(e,t){if(!e)return"";let n=e[t];if(typeof n=="string")return n;let o=e._entries;if(!Array.isArray(o))return"";let r=null;for(let s of o){if(s?.key!==t||typeof s.value!="string")continue;let i=typeof s.seq=="number"?s.seq:0;(!r||i>=r.seq)&&(r={seq:i,value:s.value})}return r?.value??""}function gi(e){for(let t of e){let n=j(t);if(!n)continue;let o=/hook boot[^"\n]*--workspace\s+'?([a-z0-9][a-z0-9_-]{0,63})'?/i.exec(n);if(o)return o[1]}return""}async function hi(e,t,n,o){let{root:r}=ge(e),s=null,i;try{s=ot(r)}catch(g){i=g.message}let c=[Le(r),Me(r),...["gemini","cursor","copilot"].map(g=>Be(g,n,t))],l=(o||So(s,"workspace")||gi(c)).toLowerCase();if(So(s,"mode")==="local"){let g=U(t),h=await oe({stateDir:g}),w=Wt(g,"token"),x=li(w)?(j(w)??"").trim():"";return{mode:"local",apiUrl:h.running&&h.url?h.url:"",apiKey:x,keySource:x?"local token file":"none",workspace:l,bindingError:i,root:r}}let{apiKey:d,apiUrl:u}=ke(t),f=u||ae,p=t.CONTEXT_API_KEY??t.CTQ_API_KEY?`env ${t.CONTEXT_API_KEY?"CONTEXT_API_KEY":"CTQ_API_KEY"}`:d?"credential file":"none";return{mode:"cloud",apiUrl:f,apiKey:d,keySource:p,workspace:l,bindingError:i,root:r}}function zt(e){let t=j(e);return t===null?{present:!1,reason:`no hook file at ${e}`}:t.includes("contextq")&&t.includes("hook boot")?{present:!0,reason:`contextq boot hook present in ${e}`}:{present:!1,reason:`${e} exists but holds no contextq boot hook`}}function mi(e,t,n,o){let r=ft(e,t,n),s=j(r);if(s===null)return{pass:!1,detail:`no MCP entry: ${r} has no contextq server`};let i=ce(s);if(!i||typeof i!="object"||!i.mcpServers||typeof i.mcpServers!="object")return{pass:!1,detail:`${r} is not valid JSON or has no mcpServers object`};let c=i.mcpServers.contextq;if(!c||typeof c!="object")return{pass:!1,detail:`no MCP entry: ${r} has no contextq server`};let l=c.httpUrl??c.url;return l===$(o)?{pass:!0,detail:`contextq entry in ${r} points at ${$(o)}`}:{pass:!1,detail:`contextq entry in ${r} points at ${typeof l=="string"?l:"an unknown URL"}, not ${$(o)}`}}function wi(e,t,n){let o=wt(e,t),r=j(o),s=r===null?null:ce(r),i=s&&typeof s=="object"?yt(s):null;return i?i.url===$(n)?{pass:!0,missing:!1,detail:`contextq entry in ${o} points at ${$(n)}`}:{pass:!1,missing:!1,detail:`contextq entry in ${o} points at ${typeof i.url=="string"?i.url:"an unknown URL"}, not ${$(n)}`}:{pass:!1,missing:!0,detail:`no contextq server in ${o}`}}function yi(e,t){let n=j(Wt(e,".claude","settings.json"));if(n!==null&&n.includes(_))return{pass:!0,detail:`${_} enabled at project scope (endpoint lives in Claude Code's store)`,viaPlugin:!0};let o=Ct(e),r=j(o);if(r===null)return{pass:!1,detail:`no MCP entry: ${o} has no contextq server`,viaPlugin:!1};let c=ce(r)?.mcpServers?.contextq;if(!c||typeof c!="object")return{pass:!1,detail:`no MCP entry: ${o} has no contextq server`,viaPlugin:!1};let l=c.env,d=c.url??l?.CONTEXT_API_URL;if(typeof d=="string"&&(d===t||/^\$\{[^}]*\}$/.test(d))){let u=d===t?`points at ${t}`:"uses a ${VAR} placeholder (key and URL resolve from the environment or credential file)";return{pass:!0,detail:`contextq entry in ${o} ${u}`,viaPlugin:!1}}return{pass:!1,detail:`contextq entry in ${o} points at ${typeof d=="string"?d:"an unknown URL"}, not ${t}`,viaPlugin:!1}}function ki(e,t){let n=Et(e),o=j(n);if(o===null)return{pass:!1,detail:`no MCP entry: ${n} has no mcp_servers.contextq`};let r;try{r=pi(o)}catch(c){return{pass:!1,detail:`${n} is not valid TOML (${c.message.split(`
42
- `)[0]}); left untouched`}}let i=r.mcp_servers?.contextq;return!i||typeof i!="object"?{pass:!1,detail:`no MCP entry: ${n} has no mcp_servers.contextq`}:i.url===$(t)?{pass:!0,detail:`mcp_servers.contextq in ${n} points at ${$(t)}`}:{pass:!1,detail:`mcp_servers.contextq in ${n} points at ${typeof i.url=="string"?i.url:"an unknown URL"}, not ${$(t)}`}}function xi(e){let t=j(Wt(e,".claude","settings.json"));if(t!==null&&t.includes(_))return{pass:!0,detail:`${_} carries the lifecycle hooks`,viaPlugin:!0};let n=zt(Le(e));return{pass:n.present,detail:n.reason,viaPlugin:!1}}async function vi(e){let t=e.cwd??process.cwd(),n=e.homeDir??ui(),o=e.env??process.env,{surfaces:r}=ge(t),s=r.filter(p=>p!=="desktop"),i=await hi(t,o,n,e.workspace),c=[],l=a(p=>c.push(p),"push");if(!i.apiUrl)l({host:null,step:"endpoint",status:"fail",detail:"the repo is bound to local mode but the local server is not running",fix:"contextq local start"});else try{let p=await Z("GET",`${i.apiUrl.replace(/\/+$/,"")}/health`,"");p.status===200?l({host:null,step:"endpoint",status:"pass",detail:`${i.apiUrl} answers its health route (HTTP 200)`}):l({host:null,step:"endpoint",status:"fail",detail:`${i.apiUrl}/health answered HTTP ${p.status}`,fix:i.mode==="local"?"contextq local start":`check that the server is up and --url points at it (now: ${i.apiUrl})`})}catch(p){l({host:null,step:"endpoint",status:"fail",detail:p.message,fix:i.mode==="local"?"contextq local start":`check that the server is up and --url points at it (now: ${i.apiUrl})`})}let d=!1;if(!i.apiKey)l({host:null,step:"credential",status:"fail",detail:i.mode==="local"?"no local token found in the edge state dir":"no API key found in CONTEXT_API_KEY/CTQ_API_KEY or the credential file",fix:i.mode==="local"?"contextq local start":"contextq init (or contextq login) to approve this machine"});else{let p=i.mode==="local"?`${i.apiUrl}/workspaces`:`${i.apiUrl.replace(/\/+$/,"")}/api/workspaces`;try{let g=await Z("GET",p,i.apiKey);g.status===200?(d=!0,l({host:null,step:"credential",status:"pass",detail:`key ${pt(i.apiKey)} from ${i.keySource} accepted by the server`})):g.status===401||g.status===403?l({host:null,step:"credential",status:"fail",detail:`the server refused the key ${pt(i.apiKey)} (HTTP ${g.status})`,fix:"contextq init --key <new key> (or contextq login) to replace it"}):l({host:null,step:"credential",status:"fail",detail:`checking the key ${pt(i.apiKey)} failed (HTTP ${g.status})`,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}catch(g){l({host:null,step:"credential",status:"fail",detail:`could not check the key ${pt(i.apiKey)}: ${g.message}`,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}}let u=!1;if(i.bindingError)l({host:null,step:"workspace",status:"fail",detail:`.contextq/config.json: ${i.bindingError}`,fix:"fix or delete .contextq/config.json, then contextq init --workspace <slug>"});else if(!i.workspace)l({host:null,step:"workspace",status:"fail",detail:`no workspace bound: ${i.root} has no workspace in .contextq/config.json and no wired boot hook names one`,fix:"contextq init --workspace <slug>, or contextq doctor --workspace <slug>"});else if(!d)l({host:null,step:"workspace",status:"skip",detail:`blocked by the credential step; workspace "${i.workspace}" not checked`});else{let p=i.apiUrl.replace(/\/+$/,"");try{let g=i.mode==="local"?`${p}/workspaces`:`${p}/api/workspaces`,h=await Z("GET",g,i.apiKey);if(h.status!==200)l({host:null,step:"workspace",status:"fail",detail:`listing workspaces failed (HTTP ${h.status})`,fix:"fix the credential step above first"});else{let w=ce(h.text),x=Array.isArray(w)?w:Array.isArray(w?.workspaces)?w.workspaces:[];if(!new Set(x.map(v=>typeof v?.slug=="string"?v.slug.toLowerCase():"")).has(i.workspace))u=!0,l({host:null,step:"workspace",status:"pass",detail:`workspace "${i.workspace}" does not exist yet; the first save creates it (check the slug if you expected existing memory there)`});else{let v=i.mode==="local"?`${p}/contexts?workspace=${encodeURIComponent(i.workspace)}&limit=1`:`${p}/api/contexts?workspace=${encodeURIComponent(i.workspace)}&limit=1`,k=await Z("GET",v,i.apiKey);k.status===200?(u=!0,l({host:null,step:"workspace",status:"pass",detail:`workspace "${i.workspace}" exists and the key can read it`})):l({host:null,step:"workspace",status:"fail",detail:`workspace "${i.workspace}" exists but the key cannot read it (HTTP ${k.status})`,fix:"check the key has read scope for this workspace, or run contextq init --key <new key>"})}}}catch(g){l({host:null,step:"workspace",status:"fail",detail:g.message,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}}for(let p of s){if(p==="opencode"){let w=wi(n,o,i.apiUrl||ae);if(w.missing){l({host:p,step:"",status:"skip",detail:"not wired",fix:"contextq init --surface opencode"});continue}l(w.pass?{host:p,step:"mcp config",status:"pass",detail:w.detail}:{host:p,step:"mcp config",status:"fail",detail:w.detail,fix:"contextq init --surface opencode"}),l({host:p,step:"boot hook",status:"skip",detail:"contextq init wires no OpenCode boot hook; memory reaches OpenCode through the MCP tools"});continue}let g,h;if(p==="claude-code")g=yi(i.root,i.apiUrl||ae),h=xi(i.root);else if(p==="codex"){g=ki(i.root,i.apiUrl||ae);let w=zt(Me(i.root));h={pass:w.present,detail:w.reason}}else{g=mi(p,n,o,i.apiUrl||ae);let w=zt(Be(p,n,o));h={pass:w.present,detail:w.reason}}if(!g.pass&&!h.pass){l({host:p,step:"",status:"skip",detail:"not wired",fix:`contextq init --surface ${p}`});continue}g.pass?l({host:p,step:"mcp config",status:"pass",detail:g.detail}):l({host:p,step:"mcp config",status:"fail",detail:g.detail,fix:`contextq init --surface ${p}`}),h.pass?l({host:p,step:"boot hook",status:"pass",detail:h.detail}):l({host:p,step:"boot hook",status:"fail",detail:h.detail,fix:`contextq init --surface ${p}`})}if(!d||!u)l({host:null,step:"round trip",status:"skip",detail:`blocked by ${d?"the workspace step":"the credential step"}; no probe note was saved`});else{let p=i.apiUrl.replace(/\/+$/,""),g=`doctor-probe-${ci().slice(0,8)}`,h=null,w=null;try{let k=i.mode==="local"?{workspace:i.workspace,type:"reference",name:`doctor probe ${g}`,content:`doctor round-trip probe ${g} -- safe to delete`,tags:["doctor"]}:{name:`doctor probe ${g}`,description:`contextq doctor round-trip probe ${g}`,content:`doctor round-trip probe ${g} -- safe to delete`,type:"reference",workspace:i.workspace,tags:["doctor"]},m=await Z("POST",i.mode==="local"?`${p}/contexts`:`${p}/api/contexts`,i.apiKey,k);if(m.status===200||m.status===201){let b=ce(m.text);typeof b?.id=="number"||typeof b?.id=="string"?h=b.id:typeof b?.ref=="string"?h=b.ref:w="the server answered 200 but returned no id or ref"}else w=`save failed (HTTP ${m.status}): ${m.text.slice(0,160)}`}catch(k){w=`save failed: ${k.message}`}let x=!1;if(h!==null)try{let k=await Z("POST",i.mode==="local"?`${p}/search`:`${p}/api/search`,i.apiKey,{query:g,workspace:i.workspace,limit:5});if(k.status===200){let m=ce(k.text);x=(Array.isArray(m?.results)?m.results:[]).some(S=>JSON.stringify(S).includes(g))}}catch{x=!1}let y=null,v=i.mode!=="local";if(h!==null&&v)try{let k=i.mode==="local"?`${p}/contexts/${encodeURIComponent(String(h))}`:`${p}/api/contexts/${encodeURIComponent(String(h))}`,m=await Z("DELETE",k,i.apiKey);m.status!==200&&m.status!==204&&(y=`delete failed (HTTP ${m.status}); note ${String(h)} left in workspace "${i.workspace}"`)}catch(k){y=`delete failed: ${k.message}; note ${String(h)} left in workspace "${i.workspace}"`}if(w!==null)l({host:null,step:"round trip",status:"fail",detail:w,fix:"fix the credential and workspace steps above first"});else if(x)l(y!==null?{host:null,step:"round trip",status:"fail",detail:`saved and recalled, but ${y}`,fix:i.mode==="local"?"the local server has no note-delete API; remove the probe note after it gains one":"delete the probe note by hand, then re-run contextq doctor"}:v?{host:null,step:"round trip",status:"pass",detail:`saved one note tagged doctor into "${i.workspace}", recalled it by search, then deleted it`}:{host:null,step:"round trip",status:"pass",detail:`saved one note tagged doctor into "${i.workspace}" and recalled it by search; the local server has no delete route, so note ${String(h)} stays`});else{let k=y?` ${y}`:" (the probe note was deleted afterwards)";l({host:null,step:"round trip",status:"fail",detail:`recall failed: the saved probe note was not returned by search for "${g}".${k}`,fix:"check search is healthy (ES optional, PG tsvector fallback), then re-run contextq doctor"})}}let f=c.every(p=>p.status!=="fail");return{mode:i.mode,apiUrl:i.apiUrl||ae,workspace:i.workspace,lines:c,ok:f}}function bi(e){return e.lines.map(t=>{if(t.status==="skip")return t.host?t.fix?`SKIP ${t.host}: ${t.detail} (run ${t.fix})`:`SKIP ${t.host} ${t.step}: ${t.detail}`:`SKIP ${t.step}: ${t.detail}`;let n=t.host?`${t.host} `:"";return t.status==="pass"?`PASS ${n}${t.step} (${t.detail})`:`FAIL ${n}${t.step}: ${t.detail}. Fix: ${t.fix??"see above"}`})}var ae,fi,_o=C(()=>{"use strict";xt();Ge();_t();Pt();Y();Rt();se();Ce();Fe();me();Ne();ae="https://app.contextq.dev",fi=8e3;a(j,"readText");a(pt,"describeKey");a(Z,"http");a(ce,"parseJson");a(So,"bindingValue");a(gi,"workspaceFromHooks");a(hi,"resolveTarget");a(zt,"checkHookFile");a(mi,"checkMcpEntry");a(wi,"checkOpencodeMcp");a(yi,"checkClaudeMcp");a(ki,"checkCodexMcp");a(xi,"checkClaudeHook");a(vi,"runDoctor");a(bi,"formatHuman")});xt();G();import{homedir as Zt}from"os";import{basename as Eo}from"path";G();var mn="the provided API key does not look valid (expected sk_live_<40 hex chars>)",ar="npx @contextq/cli init --key <sk_live_...>";function wn(e){return e.status===401||e.status===403}a(wn,"isRefused");function yn(e,t){switch(e){case"explicit":return"The --key";case"ambient":return`The ${t??"environment"}`;case"stored":return`The stored (${A()})`;case"prompt":return"The typed";case"device":return"The browser-approved"}}a(yn,"describe");async function kn(e){let t=N(),n=St()?t.apiKey:"",o,r;if(e.explicitKey){if(!I.test(e.explicitKey))throw new Error(mn);o=e.explicitKey,r="explicit"}else if(n)o=n,r="stored";else if(e.ambientKey){if(!I.test(e.ambientKey))throw new Error(mn);o=e.ambientKey,r="ambient"}else if(e.interactive&&e.deviceLogin)o=await e.deviceLogin(),r="device";else{let c=e.interactive?await e.prompt("ContextQ API key (sk_live_...): "):"";if(!I.test(c))throw new Error("no valid API key available (expected sk_live_<40 hex chars>). Run `contextq login` from an interactive terminal to approve this machine in the browser, set CONTEXT_API_KEY, or pass --key.");o=c,r="prompt"}let s=await e.verify(o);if(wn(s)){let c=`${yn(r,e.ambientVarName)} API key was refused by ${e.apiUrl} (HTTP ${s.status})`;if(!e.interactive)throw new Error(`${c}; nothing was written. Re-run with a working key: ${ar}`);if(e.deviceLogin)console.log(`${c}. Approve this machine in the browser to replace it.`),o=await e.deviceLogin(),r="device";else{let l=await e.prompt(`${c}. Paste a new key (sk_live_...): `);if(!I.test(l))throw new Error(`${c}, and no valid replacement was entered; nothing was written.`);o=l,r="prompt"}if(s=await e.verify(o),wn(s))throw new Error(`${yn(r,null)} API key was refused by ${e.apiUrl} (HTTP ${s.status}); nothing was written.`)}if(r==="device"&&!s.ok){let c=s.status?`HTTP ${s.status}`:s.error??"unknown error";throw new Error(`could not verify the browser-approved key against ${e.apiUrl} (${c}); nothing was written. Run \`contextq login\` again once the server answers.`)}return{decision:M({explicitKey:r==="explicit"||r==="prompt"||r==="device"?o:void 0,ambientVarName:e.ambientVarName,ambientKey:e.ambientKey,apiUrl:e.apiUrl}),verify:s,source:r}}a(kn,"resolveVerifiedCredentials");G();import{hostname as cr}from"os";var E=class extends Error{static{a(this,"DeviceLoginError")}code;constructor(t,n){super(n),this.name="DeviceLoginError",this.code=t}},lr=5,dr=5,ur=600;function pr(e,t){return new Promise((n,o)=>{if(t?.aborted)return o(new E("cancelled","login cancelled; nothing was written"));let r=setTimeout(()=>{t?.removeEventListener("abort",s),n()},e),s=a(()=>{clearTimeout(r),o(new E("cancelled","login cancelled; nothing was written"))},"onAbort");t?.addEventListener("abort",s,{once:!0})})}a(pr,"defaultSleep");async function xn(e){try{return await e.json()}catch{return null}}a(xn,"readJson");function vn(e,t){return typeof e=="number"&&Number.isInteger(e)&&e>0?e:t}a(vn,"positiveInt");async function He(e){let t=e.fetchImpl??fetch,n=e.sleep??pr,o=e.now??Date.now,r=e.apiUrl.replace(/\/+$/,""),s=a(()=>new E("cancelled","login cancelled; nothing was written"),"cancelled"),i;try{i=await t(`${r}/api/auth/device/start`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({client_name:e.clientName??"@contextq/cli",hostname:e.hostname??cr()}),signal:e.signal})}catch(h){throw e.signal?.aborted?s():new E("unreachable",`could not reach ${r}/api/auth/device/start (${h.message})`)}let c=i.ok?await xn(i):null,l=typeof c?.device_code=="string"?c.device_code:"",d=typeof c?.user_code=="string"?c.user_code:"";if(!l||!d)throw new E("protocol",`${r} did not start a device login (HTTP ${i.status}); is this a ContextQ server?`);let u=typeof c?.verification_uri_complete=="string"&&c.verification_uri_complete||typeof c?.verification_uri=="string"&&c.verification_uri||`${r}/link`,f=vn(c?.expires_in,ur),p=vn(c?.interval,dr);e.onPrompt({verificationUri:u,userCode:d,expiresInSeconds:f});let g=o()+f*1e3;for(;;){if(o()>=g)throw new E("expired_token","the approval window expired before this machine was approved; nothing was written");if(await n(p*1e3,e.signal),e.signal?.aborted)throw s();let h;try{h=await t(`${r}/api/auth/device/token`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({device_code:l}),signal:e.signal})}catch{if(e.signal?.aborted)throw s();continue}let w=await xn(h);if(h.status===200){let y=w?.api_key;if(typeof y=="string"&&I.test(y))return y;throw new E("protocol","the token endpoint answered 200 without a well-formed API key; nothing was written")}let x=typeof w?.error=="string"?w.error:"";switch(x){case"authorization_pending":continue;case"slow_down":p+=lr;continue;case"access_denied":throw new E("access_denied","the request was denied in the browser; nothing was written");case"expired_token":throw new E("expired_token","the device code expired; run the command again");case"invalid_grant":throw new E("invalid_grant","the server rejected the device code; run the command again");default:if(h.status>=500||h.status===429)continue;throw new E("protocol",`unexpected answer from the token endpoint (HTTP ${h.status}${x?`, ${x}`:""})`)}}}a(He,"deviceLogin");_t();Y();Ne();Pt();Rt();me();Fe();Ge();Ge();import{existsSync as os}from"fs";import{basename as rs,join as ss}from"path";var Ut=["end","compact"];function is(e){let t;try{t=JSON.parse(e).capture_scope}catch{return"metadata"}if(t==null)return"summary";let n=String(t).trim().toLowerCase();return n===""||n==="summary"||n==="auto"?"summary":n==="off"?"off":"metadata"}a(is,"localCaptureScope");function as(e){if(!e||e.trim()==="")return"";try{let t=JSON.parse(e),n=[t.sessionId,t.session_id,t.conversation_id].find(o=>typeof o=="string"&&o!=="");return typeof n=="string"?n.replace(/[^a-zA-Z0-9]/g,"").slice(0,12):""}catch{return""}}a(as,"hostSessionSuffix");function cs(e){return String(e.split(`
43
- `).filter(t=>t.trim()!=="").length)}a(cs,"countLines");async function Xn(e){let t=Dt(e.cwd,e.stdinText),n=q(t,["rev-parse","--show-toplevel"]);if(!n)return{outcome:"skipped",detail:"not inside a git repository"};if(e.requireConfig&&!os(ss(n,".contextq","config.json")))return{outcome:"skipped",detail:"repo has no .contextq/config.json"};let o=qt(n),r=is(o);if(r==="off")return{outcome:"skipped",detail:"capture_scope is off"};let{apiKey:s,apiUrl:i}=ke(e.env);if(!s)return{outcome:"skipped",detail:"no API key (run npx @contextq/cli init or login)"};let c=q(n,["status","--porcelain"]),l=new URLSearchParams({config:o,reason:e.reason,final:e.reason==="end"?"1":"0",project:rs(n),git_root:n,branch:q(n,["branch","--show-current"]),head_sha:q(n,["rev-parse","--short","HEAD"]),dirty_count:cs(c),session_suffix:as(e.stdinText),synthesis_status:"degraded",synthesis_reason:`no_synthesis_on_${e.format}`}),d=q(n,["rev-list","--count","@{upstream}..HEAD"]);/^\d+$/.test(d)&&l.set("upstream_delta",d),r==="summary"&&(l.set("recent_commits",q(n,["log","--oneline","-5"])),l.set("changed_paths",c.split(`
43
+ Edit or delete them locally and run \`contextq attach\` again, or pass --leave-denied to keep them local-only`)}l=u.pending}s.state="transferred",P(o,s),t(`[push] ${Object.keys(s.acked).length} note(s) are in ${r}`)}return s}function qe(e){return fe(e,"attach-binding-before.json")}function Ii(e){return e instanceof mt}async function wt(e,t,n,o){await De(e,t,"PUT","/sync/write-lock",{lock:n,...o?{attachedTo:o}:{}})}async function pt(e,t,n,o,r){let{restorePlugin:s,readPluginState:i}=await Promise.resolve().then(()=>(dt(),un)),{appendBinding:c}=await Promise.resolve().then(()=>(pe(),Ie)),l=e.fetchImpl??fetch,u=e.env??process.env,d=[],f=fe(o,"plugin-config.backup.json"),p=i(u);if(p.installed&&p.apiUrl&&je(p.apiUrl)!==je(n.url)&&ft(f)){let g=s(f,{apiUrl:n.url,apiKey:n.token},u);d.push(g.status==="failed"?`plugin NOT restored: ${g.reason}`:"plugin points at the local server again")}if(e.repoRoot&&ft(qe(o))){let g=JSON.parse(gn(qe(o),"utf8")),h=[];g.mode&&h.push(["mode",g.mode]),g.workspace&&h.push(["workspace",g.workspace]),h.length>0&&c(e.repoRoot,h),d.push("repo binding restored")}throw await wt(l,n,null),t.state="transferred",P(o,t),new T(`${r}; rolled back (${[...d,"local writes unlocked"].join(", ")}). Run \`contextq attach\` again to retry.`)}async function Ni(e){let t=e.log??(h=>console.log(h)),n=e.fetchImpl??fetch,o=e.env??process.env,r=e.stateDir??K(),s=je(e.cloudUrl),{reconfigurePlugin:i,readPluginState:c}=await Promise.resolve().then(()=>(dt(),un)),{appendBinding:l,readBinding:u}=await Promise.resolve().then(()=>(pe(),Ie)),d=yt(r);if(!d||!Ne(d,"transferred")||d.state==="transfer_blocked")throw new T("nothing is ready to switch: run the transfer first (`contextq attach`)");if(d.state==="attached")return d;let f=await kt({stateDir:r,runtime:e.runtime}),p=Go(s,d),g=a(h=>{if(e.crashAfter===h)throw new mt(`simulated crash after ${h}`)},"crash");if(d.state==="switching"&&(t("[info] an earlier attach stopped during the switch; redoing the switch (every step is idempotent)"),d.state="freezing",P(r,d)),d.state==="transferred"||d.state==="freezing"){d.consent={ping:e.pingConsent??d.consent.ping??!1},d.state="freezing",P(r,d),await wt(n,f,"attach"),g("freezing");let h=await De(n,f,"POST","/sync/push",{parentUrl:s,apiKey:p});for(let y of h.refs)y.outcome==="accepted"&&typeof y.cloudId=="number"&&(d.acked[y.ref]=y.cloudId);let m=d.leaveDenied?Object.keys(d.denied).length:0;if((h.failures.length>0||h.pending>m)&&await pt(e,d,f,r,`the final flush left ${h.pending} note(s) pending${h.failures[0]?` (${h.failures[0].error})`:""}`),d.state="switching",P(r,d),e.repoRoot&&!ft(qe(r))){let y=u(e.repoRoot),v={mode:y?.mode??null,workspace:y?.workspace??null};Vo(qe(r),`${JSON.stringify(v)}
44
+ `,{mode:384})}if(c(o).installed){let y=i({target:{apiUrl:s,apiKey:p},journalPath:fe(r,"plugin-config.backup.json"),previousKeySource:"local-token",env:o});y.status==="failed"&&await pt(e,d,f,r,`switching the plugin failed: ${y.reason}`),t(`[rotate] plugin -> ${s}`)}else t("[info] no installed ContextQ plugin in this Claude Code profile; only the repo binding switches");if(g("switching"),e.repoRoot){let v=u(e.repoRoot)?.workspace??"global",x=d.workspaceMap[v]??v;l(e.repoRoot,[["mode","cloud"],["workspace",x]]);for(let w of["bound-host","local-ids.json"])gt(fe(e.repoRoot,".contextq","cache",w),{force:!0});t(`[bind] .contextq/config.json -> mode=cloud workspace=${x}`)}d.state="verifying",P(r,d)}if(d.state==="verifying"){let h=e.repoRoot?u(e.repoRoot)?.workspace??"global":"global",m=await ht(n,s,p,"POST","/api/agent/boot",{agent:"contextq-attach",workspace:h}),k=Object.values(d.acked)[0],y=k===void 0?{status:200,text:""}:await ht(n,s,p,"GET",`/api/contexts/${k}`),v=m.status===200&&/"mode"\s*:\s*"local"/.test(m.text);(m.status!==200||v||y.status!==200)&&await pt(e,d,f,r,`the cloud did not verify (boot HTTP ${m.status}${v?", still local":""}, read HTTP ${y.status})`),await wt(n,f,"attached",s),d.state="attached",P(r,d),gt(qe(r),{force:!0}),t(`[check] ${s} answers boot and reads an imported note`)}return d}async function Di(e){let t=e.stateDir??K(),n=yt(t);if(!n)return"no attach in progress";if(n.state==="attached")return"the attach already finished; nothing to abort";let o=await kt({stateDir:t,runtime:e.runtime});if(["freezing","switching","verifying"].includes(n.state))try{await pt(e,n,o,t,"aborted")}catch(r){return r.message}return await wt(e.fetchImpl??fetch,o,null),`attach left at ${n.state}; local writes are unlocked`}var T,Qo,mt,Wo=S(()=>{"use strict";Oe();W();T=class extends Error{static{a(this,"AttachError")}constructor(t){super(t),this.name="AttachError"}},Qo=["login_pending","authenticated","workspaces_mapped","transferring","transfer_blocked","transferred","freezing","switching","verifying","attached"];a(Ne,"reached");a(He,"journalPath");a(yt,"readJournal");a(P,"writeJournal");a(hn,"fingerprint");a(je,"normalizeUrl");a(De,"localJson");a(ht,"cloudCall");a(kt,"ensureLocalServer");a(Go,"storedKeyFor");a(Oi,"attachTransfer");a(qe,"bindingFile");mt=class extends Error{static{a(this,"SimulatedCrash")}};a(Ii,"isSimulatedCrash");a(wt,"setLock");a(pt,"rollback");a(Ni,"attachSwitch");a(Di,"abortAttach")});var nr={};he(nr,{formatHuman:()=>zi,runDoctor:()=>Gi});import{randomUUID as qi}from"crypto";import{existsSync as Hi,readFileSync as ji}from"fs";import{homedir as Ui}from"os";import{join as vt}from"path";import{parse as tr}from"smol-toml";function q(e){try{return ji(e,"utf-8")}catch{return null}}function xt(e){let t=e.startsWith("sk_live_")?"sk_live_":e.startsWith("ctq_local_")?"ctq_local_":null;return t?`prefix="${t}" length=${e.length}`:`unrecognized format length=${e.length}`}async function te(e,t,n,o){let r;try{r=await fetch(t,{method:e,headers:{...n?{Authorization:`Bearer ${n}`}:{},...o!==void 0?{"content-type":"application/json"}:{}},...o!==void 0?{body:JSON.stringify(o)}:{},signal:AbortSignal.timeout(Li)})}catch(s){throw new Error(`could not reach ${t} (${s.message})`)}return{status:r.status,text:await r.text()}}function V(e){try{return e.trim()===""?null:JSON.parse(e)}catch{return null}}function Zo(e,t){if(!e)return"";let n=e[t];if(typeof n=="string")return n;let o=e._entries;if(!Array.isArray(o))return"";let r=null;for(let s of o){if(s?.key!==t||typeof s.value!="string")continue;let i=typeof s.seq=="number"?s.seq:0;(!r||i>=r.seq)&&(r={seq:i,value:s.value})}return r?.value??""}function Ki(e){for(let t of e){let n=q(t);if(!n)continue;let o=/hook boot[^"\n]*--workspace\s+'?([a-z0-9][a-z0-9_-]{0,63})'?/i.exec(n);if(o)return o[1]}return""}async function Mi(e,t,n,o){let{root:r}=ke(e),s=null,i;try{s=ut(r)}catch(g){i=g.message}let c=[xe(r),ve(r),...["gemini","cursor","copilot"].map(g=>be(g,n,t))],l=(o||Zo(s,"workspace")||Ki(c)).toLowerCase();if(Zo(s,"mode")==="local"){let g=K(t),h=await de({stateDir:g}),m=vt(g,"token"),k=Hi(m)?(q(m)??"").trim():"";return{mode:"local",apiUrl:h.running&&h.url?h.url:"",apiKey:k,keySource:k?"local token file":"none",workspace:l,bindingError:i,root:r}}let{apiKey:u,apiUrl:d}=Ee(t),f=d||ne,p=t.CONTEXT_API_KEY??t.CTQ_API_KEY?`env ${t.CONTEXT_API_KEY?"CONTEXT_API_KEY":"CTQ_API_KEY"}`:u?"credential file":"none";return{mode:"cloud",apiUrl:f,apiKey:u,keySource:p,workspace:l,bindingError:i,root:r}}function mn(e){let t=q(e);return t===null?{present:!1,reason:`no hook file at ${e}`}:t.includes("contextq")&&t.includes("hook boot")?{present:!0,reason:`contextq boot hook present in ${e}`}:{present:!1,reason:`${e} exists but holds no contextq boot hook`}}function Fi(e,t,n,o){let r=Ke(e,t,n),s=q(r);if(s===null)return{pass:!1,detail:`no MCP entry: ${r} has no contextq server`};let i=V(s);if(!i||typeof i!="object"||!i.mcpServers||typeof i.mcpServers!="object")return{pass:!1,detail:`${r} is not valid JSON or has no mcpServers object`};let c=i.mcpServers.contextq;if(!c||typeof c!="object")return{pass:!1,detail:`no MCP entry: ${r} has no contextq server`};let l=c.httpUrl??c.url;return l===$(o)?{pass:!0,detail:`contextq entry in ${r} points at ${$(o)}`}:{pass:!1,detail:`contextq entry in ${r} points at ${typeof l=="string"?l:"an unknown URL"}, not ${$(o)}`}}function Ji(e,t,n){let o=Pt(e,t),r=q(o),s=r===null?null:V(r),i=s&&typeof s=="object"?Tt(s):null;return i?i.url===$(n)?{pass:!0,missing:!1,detail:`contextq entry in ${o} points at ${$(n)}`}:{pass:!1,missing:!1,detail:`contextq entry in ${o} points at ${typeof i.url=="string"?i.url:"an unknown URL"}, not ${$(n)}`}:{pass:!1,missing:!0,detail:`no contextq server in ${o}`}}function Bi(e,t){let n=q(vt(e,".claude","settings.json"));if(n!==null&&n.includes(E))return{pass:!0,detail:`${E} enabled at project scope (endpoint lives in Claude Code's store)`,viaPlugin:!0};let o=qt(e),r=q(o);if(r===null)return{pass:!1,detail:`no MCP entry: ${o} has no contextq server`,viaPlugin:!1};let c=V(r)?.mcpServers?.contextq;if(!c||typeof c!="object")return{pass:!1,detail:`no MCP entry: ${o} has no contextq server`,viaPlugin:!1};let l=c.env,u=c.url??l?.CONTEXT_API_URL;if(typeof u=="string"&&(u===t||/^\$\{[^}]*\}$/.test(u))){let d=u===t?`points at ${t}`:"uses a ${VAR} placeholder (key and URL resolve from the environment or credential file)";return{pass:!0,detail:`contextq entry in ${o} ${d}`,viaPlugin:!1}}return{pass:!1,detail:`contextq entry in ${o} points at ${typeof u=="string"?u:"an unknown URL"}, not ${t}`,viaPlugin:!1}}function Xi(e,t){let n=ze(e),o=q(n);if(o===null)return{pass:!1,detail:`no MCP entry: ${n} has no mcp_servers.contextq`};let r;try{r=tr(o)}catch(c){return{pass:!1,detail:`${n} is not valid TOML (${c.message.split(`
45
+ `)[0]}); left untouched`}}let i=r.mcp_servers?.contextq;return!i||typeof i!="object"?{pass:!1,detail:`no MCP entry: ${n} has no mcp_servers.contextq`}:i.url===$(t)?{pass:!0,detail:`mcp_servers.contextq in ${n} points at ${$(t)}`}:{pass:!1,detail:`mcp_servers.contextq in ${n} points at ${typeof i.url=="string"?i.url:"an unknown URL"}, not ${$(t)}`}}function Yi(e,t,n,o,r,s){if(e==="opencode")return null;if(e==="claude-code"){let u=q(vt(t,".mcp.json")),d=V(u??"")?.mcpServers?.contextq;if(!d||typeof d!="object")return null;let f=typeof d.env?.CONTEXT_API_KEY=="string"&&!/^\$\{[^}]*\}$/.test(d.env.CONTEXT_API_KEY),p=Ht(d,r,f,s);return p?`.mcp.json contextq entry is stale (${p})`:null}if(e==="codex"){let u=q(ze(t));if(u===null)return null;try{let d=tr(u).mcp_servers?.contextq,f=d&&typeof d=="object"?Ge(d,r,s):null;return f?`mcp_servers.contextq is stale (${f})`:null}catch{return null}}let i=q(Ke(e,n,o)),c=V(i??"")?.mcpServers?.contextq,l=c&&typeof c=="object"?St(e,c,r,s):null;return l?`contextq MCP entry is stale (${l})`:null}function Qi(e,t,n,o){let r=o.CONTEXTQ_HOOK_RUNNER||D;return e==="claude-code"?Mt(xe(t),"claude",r):e==="codex"?Mt(ve(t),"codex",r):e==="opencode"?[]:co(e,be(e,n,o),r)}function Vi(e){let t=q(vt(e,".claude","settings.json"));if(t!==null&&t.includes(E))return{pass:!0,detail:`${E} carries the lifecycle hooks`,viaPlugin:!0};let n=mn(xe(e));return{pass:n.present,detail:n.reason,viaPlugin:!1}}async function Gi(e){let t=e.cwd??process.cwd(),n=e.homeDir??Ui(),o=e.env??process.env,{surfaces:r}=ke(t),s=r.filter(p=>p!=="desktop"),i=await Mi(t,o,n,e.workspace),c=[],l=a(p=>c.push(p),"push");if(!i.apiUrl)l({host:null,step:"endpoint",status:"fail",detail:"the repo is bound to local mode but the local server is not running",fix:"contextq local start"});else try{let p=await te("GET",`${i.apiUrl.replace(/\/+$/,"")}/health`,"");p.status===200?l({host:null,step:"endpoint",status:"pass",detail:`${i.apiUrl} answers its health route (HTTP 200)`}):l({host:null,step:"endpoint",status:"fail",detail:`${i.apiUrl}/health answered HTTP ${p.status}`,fix:i.mode==="local"?"contextq local start":`check that the server is up and --url points at it (now: ${i.apiUrl})`})}catch(p){l({host:null,step:"endpoint",status:"fail",detail:p.message,fix:i.mode==="local"?"contextq local start":`check that the server is up and --url points at it (now: ${i.apiUrl})`})}let u=!1;if(!i.apiKey)l({host:null,step:"credential",status:"fail",detail:i.mode==="local"?"no local token found in the edge state dir":"no API key found in CONTEXT_API_KEY/CTQ_API_KEY or the credential file",fix:i.mode==="local"?"contextq local start":"contextq init (or contextq login) to approve this machine"});else{let p=i.mode==="local"?`${i.apiUrl}/workspaces`:`${i.apiUrl.replace(/\/+$/,"")}/api/workspaces`;try{let g=await te("GET",p,i.apiKey);g.status===200?(u=!0,l({host:null,step:"credential",status:"pass",detail:`key ${xt(i.apiKey)} from ${i.keySource} accepted by the server`})):g.status===401||g.status===403?l({host:null,step:"credential",status:"fail",detail:`the server refused the key ${xt(i.apiKey)} (HTTP ${g.status})`,fix:"contextq init --key <new key> (or contextq login) to replace it"}):l({host:null,step:"credential",status:"fail",detail:`checking the key ${xt(i.apiKey)} failed (HTTP ${g.status})`,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}catch(g){l({host:null,step:"credential",status:"fail",detail:`could not check the key ${xt(i.apiKey)}: ${g.message}`,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}}let d=!1;if(i.bindingError)l({host:null,step:"workspace",status:"fail",detail:`.contextq/config.json: ${i.bindingError}`,fix:"fix or delete .contextq/config.json, then contextq init --workspace <slug>"});else if(!i.workspace)l({host:null,step:"workspace",status:"fail",detail:`no workspace bound: ${i.root} has no workspace in .contextq/config.json and no wired boot hook names one`,fix:"contextq init --workspace <slug>, or contextq doctor --workspace <slug>"});else if(!u)l({host:null,step:"workspace",status:"skip",detail:`blocked by the credential step; workspace "${i.workspace}" not checked`});else{let p=i.apiUrl.replace(/\/+$/,"");try{let g=i.mode==="local"?`${p}/workspaces`:`${p}/api/workspaces`,h=await te("GET",g,i.apiKey);if(h.status!==200)l({host:null,step:"workspace",status:"fail",detail:`listing workspaces failed (HTTP ${h.status})`,fix:"fix the credential step above first"});else{let m=V(h.text),k=Array.isArray(m)?m:Array.isArray(m?.workspaces)?m.workspaces:[];if(!new Set(k.map(v=>typeof v?.slug=="string"?v.slug.toLowerCase():"")).has(i.workspace))d=!0,l({host:null,step:"workspace",status:"pass",detail:`workspace "${i.workspace}" does not exist yet; the first save creates it (check the slug if you expected existing memory there)`});else{let v=i.mode==="local"?`${p}/contexts?workspace=${encodeURIComponent(i.workspace)}&limit=1`:`${p}/api/contexts?workspace=${encodeURIComponent(i.workspace)}&limit=1`,x=await te("GET",v,i.apiKey);x.status===200?(d=!0,l({host:null,step:"workspace",status:"pass",detail:`workspace "${i.workspace}" exists and the key can read it`})):l({host:null,step:"workspace",status:"fail",detail:`workspace "${i.workspace}" exists but the key cannot read it (HTTP ${x.status})`,fix:"check the key has read scope for this workspace, or run contextq init --key <new key>"})}}}catch(g){l({host:null,step:"workspace",status:"fail",detail:g.message,fix:i.mode==="local"?"contextq local start":`check that the server is up (now: ${i.apiUrl})`})}}for(let p of s){if(p==="opencode"){let m=Ji(n,o,i.apiUrl||ne);if(m.missing){l({host:p,step:"",status:"skip",detail:"not wired",fix:"contextq init --surface opencode"});continue}l(m.pass?{host:p,step:"mcp config",status:"pass",detail:m.detail}:{host:p,step:"mcp config",status:"fail",detail:m.detail,fix:"contextq init --surface opencode"}),l({host:p,step:"boot hook",status:"skip",detail:"contextq init wires no OpenCode boot hook; memory reaches OpenCode through the MCP tools"});continue}let g,h;if(p==="claude-code")g=Bi(i.root,i.apiUrl||ne),h=Vi(i.root);else if(p==="codex"){g=Xi(i.root,i.apiUrl||ne);let m=mn(ve(i.root));h={pass:m.present,detail:m.reason}}else{g=Fi(p,n,o,i.apiUrl||ne);let m=mn(be(p,n,o));h={pass:m.present,detail:m.reason}}if(!g.pass&&!h.pass){l({host:p,step:"",status:"skip",detail:"not wired",fix:`contextq init --surface ${p}`});continue}if(g.pass?l({host:p,step:"mcp config",status:"pass",detail:g.detail}):l({host:p,step:"mcp config",status:"fail",detail:g.detail,fix:`contextq init --surface ${p}`}),h.pass?l({host:p,step:"boot hook",status:"pass",detail:h.detail}):l({host:p,step:"boot hook",status:"fail",detail:h.detail,fix:`contextq init --surface ${p}`}),g.pass){let m=Yi(p,i.root,n,o,i.apiUrl||ne,i.apiKey);m&&l({host:p,step:"mcp config",status:"warn",detail:m,fix:er})}if(h.pass)for(let m of Qi(p,i.root,n,o))l({host:p,step:"boot hook",status:"warn",detail:m,fix:er})}if(!u||!d)l({host:null,step:"round trip",status:"skip",detail:`blocked by ${u?"the workspace step":"the credential step"}; no probe note was saved`});else{let p=i.apiUrl.replace(/\/+$/,""),g=`doctor-probe-${qi().slice(0,8)}`,h=null,m=null;try{let x=i.mode==="local"?{workspace:i.workspace,type:"reference",name:`doctor probe ${g}`,content:`doctor round-trip probe ${g} -- safe to delete`,tags:["doctor"]}:{name:`doctor probe ${g}`,description:`contextq doctor round-trip probe ${g}`,content:`doctor round-trip probe ${g} -- safe to delete`,type:"reference",workspace:i.workspace,tags:["doctor"]},w=await te("POST",i.mode==="local"?`${p}/contexts`:`${p}/api/contexts`,i.apiKey,x);if(w.status===200||w.status===201){let b=V(w.text);typeof b?.id=="number"||typeof b?.id=="string"?h=b.id:typeof b?.ref=="string"?h=b.ref:m="the server answered 200 but returned no id or ref"}else m=`save failed (HTTP ${w.status}): ${w.text.slice(0,160)}`}catch(x){m=`save failed: ${x.message}`}let k=!1;if(h!==null)try{let x=await te("POST",i.mode==="local"?`${p}/search`:`${p}/api/search`,i.apiKey,{query:g,workspace:i.workspace,limit:5});if(x.status===200){let w=V(x.text);k=(Array.isArray(w?.results)?w.results:[]).some(C=>JSON.stringify(C).includes(g))}}catch{k=!1}let y=null,v=i.mode!=="local";if(h!==null&&v)try{let x=i.mode==="local"?`${p}/contexts/${encodeURIComponent(String(h))}`:`${p}/api/contexts/${encodeURIComponent(String(h))}`,w=await te("DELETE",x,i.apiKey);w.status!==200&&w.status!==204&&(y=`delete failed (HTTP ${w.status}); note ${String(h)} left in workspace "${i.workspace}"`)}catch(x){y=`delete failed: ${x.message}; note ${String(h)} left in workspace "${i.workspace}"`}if(m!==null)l({host:null,step:"round trip",status:"fail",detail:m,fix:"fix the credential and workspace steps above first"});else if(k)l(y!==null?{host:null,step:"round trip",status:"fail",detail:`saved and recalled, but ${y}`,fix:i.mode==="local"?"the local server has no note-delete API; remove the probe note after it gains one":"delete the probe note by hand, then re-run contextq doctor"}:v?{host:null,step:"round trip",status:"pass",detail:`saved one note tagged doctor into "${i.workspace}", recalled it by search, then deleted it`}:{host:null,step:"round trip",status:"pass",detail:`saved one note tagged doctor into "${i.workspace}" and recalled it by search; the local server has no delete route, so note ${String(h)} stays`});else{let x=y?` ${y}`:" (the probe note was deleted afterwards)";l({host:null,step:"round trip",status:"fail",detail:`recall failed: the saved probe note was not returned by search for "${g}".${x}`,fix:"check search is healthy (ES optional, PG tsvector fallback), then re-run contextq doctor"})}}let f=c.every(p=>p.status!=="fail");return{mode:i.mode,apiUrl:i.apiUrl||ne,workspace:i.workspace,lines:c,ok:f}}function zi(e){return e.lines.map(t=>{if(t.status==="skip")return t.host?t.fix?`SKIP ${t.host}: ${t.detail} (run ${t.fix})`:`SKIP ${t.host} ${t.step}: ${t.detail}`:`SKIP ${t.step}: ${t.detail}`;let n=t.host?`${t.host} `:"";return t.status==="pass"?`PASS ${n}${t.step} (${t.detail})`:t.status==="warn"?`WARN ${n}${t.step}: ${t.detail}. Fix: ${t.fix??"see above"}`:`FAIL ${n}${t.step}: ${t.detail}. Fix: ${t.fix??"see above"}`})}var ne,Li,er,or=S(()=>{"use strict";Rt();st();jt();Lt();z();Qt();pe();Oe();Ze();$e();Me();ne="https://app.contextq.dev",Li=8e3;a(q,"readText");a(xt,"describeKey");a(te,"http");a(V,"parseJson");a(Zo,"bindingValue");a(Ki,"workspaceFromHooks");a(Mi,"resolveTarget");a(mn,"checkHookFile");a(Fi,"checkMcpEntry");a(Ji,"checkOpencodeMcp");a(Bi,"checkClaudeMcp");a(Xi,"checkCodexMcp");er="npx @contextq/cli init --refresh";a(Yi,"staleMcpDetail");a(Qi,"staleHookDetails");a(Vi,"checkClaudeHook");a(Gi,"runDoctor");a(zi,"formatHuman")});Rt();W();import{homedir as wn}from"os";import{basename as rr}from"path";W();var Nn="the provided API key does not look valid (expected sk_live_<40 hex chars>)",jr="npx @contextq/cli init --key <sk_live_...>";function Dn(e){return e.status===401||e.status===403}a(Dn,"isRefused");function qn(e,t){switch(e){case"explicit":return"The --key";case"ambient":return`The ${t??"environment"}`;case"stored":return`The stored (${A()})`;case"prompt":return"The typed";case"device":return"The browser-approved"}}a(qn,"describe");async function Hn(e){let t=H(),n=Dt()?t.apiKey:"",o,r;if(e.explicitKey){if(!N.test(e.explicitKey))throw new Error(Nn);o=e.explicitKey,r="explicit"}else if(n)o=n,r="stored";else if(e.ambientKey){if(!N.test(e.ambientKey))throw new Error(Nn);o=e.ambientKey,r="ambient"}else if(e.interactive&&e.deviceLogin)o=await e.deviceLogin(),r="device";else{let c=e.interactive?await e.prompt("ContextQ API key (sk_live_...): "):"";if(!N.test(c))throw new Error("no valid API key available (expected sk_live_<40 hex chars>). Run `contextq login` from an interactive terminal to approve this machine in the browser, set CONTEXT_API_KEY, or pass --key.");o=c,r="prompt"}let s=await e.verify(o);if(Dn(s)){let c=`${qn(r,e.ambientVarName)} API key was refused by ${e.apiUrl} (HTTP ${s.status})`;if(!e.interactive)throw new Error(`${c}; nothing was written. Re-run with a working key: ${jr}`);if(e.deviceLogin)console.log(`${c}. Approve this machine in the browser to replace it.`),o=await e.deviceLogin(),r="device";else{let l=await e.prompt(`${c}. Paste a new key (sk_live_...): `);if(!N.test(l))throw new Error(`${c}, and no valid replacement was entered; nothing was written.`);o=l,r="prompt"}if(s=await e.verify(o),Dn(s))throw new Error(`${qn(r,null)} API key was refused by ${e.apiUrl} (HTTP ${s.status}); nothing was written.`)}if(r==="device"&&!s.ok){let c=s.status?`HTTP ${s.status}`:s.error??"unknown error";throw new Error(`could not verify the browser-approved key against ${e.apiUrl} (${c}); nothing was written. Run \`contextq login\` again once the server answers.`)}return{decision:B({explicitKey:r==="explicit"||r==="prompt"||r==="device"?o:void 0,ambientVarName:e.ambientVarName,ambientKey:e.ambientKey,apiUrl:e.apiUrl}),verify:s,source:r}}a(Hn,"resolveVerifiedCredentials");W();import{hostname as Ur}from"os";var _=class extends Error{static{a(this,"DeviceLoginError")}code;constructor(t,n){super(n),this.name="DeviceLoginError",this.code=t}},Lr=5,Kr=5,Mr=600;function Fr(e,t){return new Promise((n,o)=>{if(t?.aborted)return o(new _("cancelled","login cancelled; nothing was written"));let r=setTimeout(()=>{t?.removeEventListener("abort",s),n()},e),s=a(()=>{clearTimeout(r),o(new _("cancelled","login cancelled; nothing was written"))},"onAbort");t?.addEventListener("abort",s,{once:!0})})}a(Fr,"defaultSleep");async function jn(e){try{return await e.json()}catch{return null}}a(jn,"readJson");function Un(e,t){return typeof e=="number"&&Number.isInteger(e)&&e>0?e:t}a(Un,"positiveInt");async function Be(e){let t=e.fetchImpl??fetch,n=e.sleep??Fr,o=e.now??Date.now,r=e.apiUrl.replace(/\/+$/,""),s=a(()=>new _("cancelled","login cancelled; nothing was written"),"cancelled"),i;try{i=await t(`${r}/api/auth/device/start`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({client_name:e.clientName??"@contextq/cli",hostname:e.hostname??Ur()}),signal:e.signal})}catch(h){throw e.signal?.aborted?s():new _("unreachable",`could not reach ${r}/api/auth/device/start (${h.message})`)}let c=i.ok?await jn(i):null,l=typeof c?.device_code=="string"?c.device_code:"",u=typeof c?.user_code=="string"?c.user_code:"";if(!l||!u)throw new _("protocol",`${r} did not start a device login (HTTP ${i.status}); is this a ContextQ server?`);let d=typeof c?.verification_uri_complete=="string"&&c.verification_uri_complete||typeof c?.verification_uri=="string"&&c.verification_uri||`${r}/link`,f=Un(c?.expires_in,Mr),p=Un(c?.interval,Kr);e.onPrompt({verificationUri:d,userCode:u,expiresInSeconds:f});let g=o()+f*1e3;for(;;){if(o()>=g)throw new _("expired_token","the approval window expired before this machine was approved; nothing was written");if(await n(p*1e3,e.signal),e.signal?.aborted)throw s();let h;try{h=await t(`${r}/api/auth/device/token`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({device_code:l}),signal:e.signal})}catch{if(e.signal?.aborted)throw s();continue}let m=await jn(h);if(h.status===200){let y=m?.api_key;if(typeof y=="string"&&N.test(y))return y;throw new _("protocol","the token endpoint answered 200 without a well-formed API key; nothing was written")}let k=typeof m?.error=="string"?m.error:"";switch(k){case"authorization_pending":continue;case"slow_down":p+=Lr;continue;case"access_denied":throw new _("access_denied","the request was denied in the browser; nothing was written");case"expired_token":throw new _("expired_token","the device code expired; run the command again");case"invalid_grant":throw new _("invalid_grant","the server rejected the device code; run the command again");default:if(h.status>=500||h.status===429)continue;throw new _("protocol",`unexpected answer from the token endpoint (HTTP ${h.status}${k?`, ${k}`:""})`)}}}a(Be,"deviceLogin");jt();z();Me();Lt();Qt();$e();Ze();st();st();import{existsSync as Rs}from"fs";import{basename as Os,join as Is}from"path";var tn=["end","compact"];function Ns(e){let t;try{t=JSON.parse(e).capture_scope}catch{return"metadata"}if(t==null)return"summary";let n=String(t).trim().toLowerCase();return n===""||n==="summary"||n==="auto"?"summary":n==="off"?"off":"metadata"}a(Ns,"localCaptureScope");function Ds(e){if(!e||e.trim()==="")return"";try{let t=JSON.parse(e),n=[t.sessionId,t.session_id,t.conversation_id].find(o=>typeof o=="string"&&o!=="");return typeof n=="string"?n.replace(/[^a-zA-Z0-9]/g,"").slice(0,12):""}catch{return""}}a(Ds,"hostSessionSuffix");function qs(e){return String(e.split(`
46
+ `).filter(t=>t.trim()!=="").length)}a(qs,"countLines");async function xo(e){let t=Wt(e.cwd,e.stdinText),n=U(t,["rev-parse","--show-toplevel"]);if(!n)return{outcome:"skipped",detail:"not inside a git repository"};if(e.requireConfig&&!Rs(Is(n,".contextq","config.json")))return{outcome:"skipped",detail:"repo has no .contextq/config.json"};let o=Zt(n),r=Ns(o);if(r==="off")return{outcome:"skipped",detail:"capture_scope is off"};let{apiKey:s,apiUrl:i}=Ee(e.env);if(!s)return{outcome:"skipped",detail:"no API key (run npx @contextq/cli init or login)"};let c=U(n,["status","--porcelain"]),l=new URLSearchParams({config:o,reason:e.reason,final:e.reason==="end"?"1":"0",project:Os(n),git_root:n,branch:U(n,["branch","--show-current"]),head_sha:U(n,["rev-parse","--short","HEAD"]),dirty_count:qs(c),session_suffix:Ds(e.stdinText),synthesis_status:"degraded",synthesis_reason:`no_synthesis_on_${e.format}`}),u=U(n,["rev-list","--count","@{upstream}..HEAD"]);/^\d+$/.test(u)&&l.set("upstream_delta",u),r==="summary"&&(l.set("recent_commits",U(n,["log","--oneline","-5"])),l.set("changed_paths",c.split(`
44
47
  `).slice(0,20).join(`
45
- `)));let u=e.fetchImpl??fetch;try{let f=await u(`${i}/api/agent/hooks/snapshot`,{method:"POST",headers:{Authorization:`Bearer ${s}`,Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:l.toString(),signal:AbortSignal.timeout(e.timeoutMs??1e4)});return await f.text(),f.status===204?{outcome:"skipped",detail:"server kept a better handoff or capture is off",status:f.status}:f.status>=200&&f.status<300?{outcome:"sent",detail:`snapshot (${e.reason}) saved`,status:f.status}:{outcome:"failed",detail:`${i} answered HTTP ${f.status}`,status:f.status}}catch(f){return{outcome:"failed",detail:`${i} unreachable (${f.message})`}}}a(Xn,"runHookSnapshot");import{appendFileSync as ls,existsSync as Yn,readFileSync as ds}from"fs";import{execFileSync as us}from"child_process";import{join as jt}from"path";var ps=[".contextq/tasks/TASKS.md",".contextq/tasks/tasks/T*.md",".contextq/tasks/.render*",".contextq/tasks/status.log"];function fs(e){try{return us("git",["-C",e,"ls-files","--error-unmatch","--",".contextq/tasks/TASKS.md"],{stdio:"ignore"}),!0}catch{return!1}}a(fs,"manifestTracked");function Gn(e){let t=jt(e,".gitignore");if(!Yn(jt(e,".git")))return{path:t,action:"skipped",detail:"not a git repo -- no board render to keep out of commits"};if(fs(e))return{path:jt(e,".contextq","tasks","TASKS.md"),action:"skipped",detail:"this repo tracks a git-sourced board -- left untouched"};let n=Yn(t)?ds(t,"utf-8"):"",o=new Set(n.split(`
46
- `).map(i=>i.trim())),r=ps.filter(i=>!o.has(i));if(r.length===0)return{path:t,action:"skipped",detail:"board render already gitignored"};let s=n===""||n.endsWith(`
48
+ `)));let d=e.fetchImpl??fetch;try{let f=await d(`${i}/api/agent/hooks/snapshot`,{method:"POST",headers:{Authorization:`Bearer ${s}`,Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:l.toString(),signal:AbortSignal.timeout(e.timeoutMs??1e4)});return await f.text(),f.status===204?{outcome:"skipped",detail:"server kept a better handoff or capture is off",status:f.status}:f.status>=200&&f.status<300?{outcome:"sent",detail:`snapshot (${e.reason}) saved`,status:f.status}:{outcome:"failed",detail:`${i} answered HTTP ${f.status}`,status:f.status}}catch(f){return{outcome:"failed",detail:`${i} unreachable (${f.message})`}}}a(xo,"runHookSnapshot");import{appendFileSync as Hs,existsSync as vo,readFileSync as js}from"fs";import{execFileSync as Us}from"child_process";import{join as nn}from"path";var Ls=[".contextq/tasks/TASKS.md",".contextq/tasks/tasks/T*.md",".contextq/tasks/.render*",".contextq/tasks/status.log"];function Ks(e){try{return Us("git",["-C",e,"ls-files","--error-unmatch","--",".contextq/tasks/TASKS.md"],{stdio:"ignore"}),!0}catch{return!1}}a(Ks,"manifestTracked");function bo(e){let t=nn(e,".gitignore");if(!vo(nn(e,".git")))return{path:t,action:"skipped",detail:"not a git repo -- no board render to keep out of commits"};if(Ks(e))return{path:nn(e,".contextq","tasks","TASKS.md"),action:"skipped",detail:"this repo tracks a git-sourced board -- left untouched"};let n=vo(t)?js(t,"utf-8"):"",o=new Set(n.split(`
49
+ `).map(i=>i.trim())),r=Ls.filter(i=>!o.has(i));if(r.length===0)return{path:t,action:"skipped",detail:"board render already gitignored"};let s=n===""||n.endsWith(`
47
50
  `)?"":`
48
- `;return ls(t,`${s}${r.join(`
51
+ `;return Hs(t,`${s}${r.join(`
49
52
  `)}
50
- `),{path:t,action:"created",detail:"gitignored the task board render (.contextq/tasks/TASKS.md is a read-only copy of the cloud board, never committed)"}}a(Gn,"initTasksDir");function zn(e){console.log(`
53
+ `),{path:t,action:"created",detail:"gitignored the task board render (.contextq/tasks/TASKS.md is a read-only copy of the cloud board, never committed)"}}a(bo,"initTasksDir");function $o(e){console.log(`
51
54
  No git repo or existing Claude Code project found here, so there is nothing on
52
55
  disk to scaffold. For Claude Desktop, Cowork, or any other MCP-capable client
53
56
  that reads a manually-edited config file, connect ContextQ in 3 steps:
@@ -76,10 +79,11 @@ There is no auto-connect for Desktop/Cowork today -- these clients only read
76
79
  their config file on start, so step 2 has to be a manual paste. Re-run
77
80
  "npx @contextq/cli init" inside a git repo (or an existing Claude Code project) to
78
81
  get the automatic .mcp.json + hook + task-manifest scaffolding instead.
79
- `)}a(zn,"printDesktopGuide");async function Wn(e){return process.stdin.isTTY?new Promise(t=>{let n=process.stdin,o=process.stdout;o.write(e);let r="",s=a(i=>{let c=i.toString("utf8");switch(c){case`
82
+ `)}a($o,"printDesktopGuide");async function So(e){return process.stdin.isTTY?new Promise(t=>{let n=process.stdin,o=process.stdout;o.write(e);let r="",s=a(i=>{let c=i.toString("utf8");switch(c){case`
80
83
  `:case"\r":case"":n.setRawMode?.(!1),n.pause(),n.removeListener("data",s),o.write(`
81
84
  `),t(r.trim());break;case"":o.write(`
82
- `),process.exit(130);break;case"\x7F":case"\b":r.length>0&&(r=r.slice(0,-1),o.write("\b \b"));break;default:r+=c,o.write("*")}},"onData");n.setRawMode?.(!0),n.resume(),n.setEncoding("utf8"),n.on("data",s)}):""}a(Wn,"promptHidden");Xe();import{existsSync as Kt,mkdirSync as Zn,readdirSync as gs,readFileSync as eo,statSync as hs,writeFileSync as ms}from"fs";import{join as ze}from"path";function ws(e,t){let n=new RegExp(`^ {2}${t}: "((?:[^"\\\\]|\\\\.)*)"$`,"m"),o=e.match(n);return o?o[1].replace(/\\(.)/g,"$1"):null}a(ws,"extractFrontmatterMetadataValue");function ys(e){return ws(e,"cluster_key")}a(ys,"extractClusterKey");function ks(e){if(!Kt(e))return[];let t=[];for(let n of gs(e)){let o=ze(e,n);if(!hs(o).isDirectory())continue;let r=ze(o,"SKILL.md");if(!Kt(r))continue;let s=eo(r,"utf-8"),i=ys(s);i&&t.push({folder:n,clusterKey:i})}return t}a(ks,"scanLocalSkills");function xs(e,t){return e.replace(/^name: .+$/m,`name: ${t}`)}a(xs,"withFrontmatterName");async function to(e){Zn(e.dir,{recursive:!0});let t=ks(e.dir),n=new Map(t.map(c=>[c.clusterKey,c.folder])),o=new Set(t.map(c=>c.folder)),r=await e.fetchSkills(),s={written:[],updated:[],unchanged:[],orphans:[]},i=new Set;for(let c of r){i.add(c.clusterKey);let d=n.get(c.clusterKey);d||(d=o.has(c.slug)?`${c.slug}-c${c.contextId}`:c.slug,o.add(d),n.set(c.clusterKey,d));let u=d===c.slug?c.markdown:xs(c.markdown,d),f=ze(e.dir,d),p=ze(f,"SKILL.md"),g=Kt(p)?eo(p,"utf-8"):null;if(g===u){s.unchanged.push(d);continue}Zn(f,{recursive:!0}),ms(p,u,"utf-8"),s[g===null?"written":"updated"].push(d)}for(let c of t)i.has(c.clusterKey)||s.orphans.push(c.folder);return s}a(to,"syncSkills");var V="https://app.contextq.dev";function $i(e){let t=e.length>0&&!e[0].startsWith("-"),n={command:t?e[0]:"",embedKey:!1,noPlugin:!1,help:!1,local:!1};for(let o=t?1:0;o<e.length;o++){let r=e[o];switch(r){case"--key":n.key=e[++o];break;case"--url":n.url=e[++o];break;case"--workspace":n.workspace=e[++o];break;case"--agent":n.agent=e[++o];break;case"--embed-key":n.embedKey=!0;break;case"--no-plugin":n.noPlugin=!0;break;case"--local":n.local=!0;break;case"--port":{let s=e[++o]??"",i=/^\d+$/.test(s)?Number(s):NaN;if(!Number.isInteger(i)||i<1||i>65535)throw new Error(`invalid port "${s}"; use a number from 1 to 65535`);n.port=i;break}case"--surface":{let s=e[++o];if(s!=="claude-code"&&s!=="codex"&&s!=="opencode"&&!ee.includes(s))throw new Error(`--surface value "${s}" is invalid -- allowed: claude-code, codex, ${ee.join(", ")}, opencode`);n.surface=s;break}case"-h":case"--help":n.help=!0;break;default:throw new Error(`unknown argument: ${r}`)}}return n}a($i,"parseArgs");function Oe(){console.log(`contextq \u2014 one-command installer for ContextQ
85
+ `),process.exit(130);break;case"\x7F":case"\b":r.length>0&&(r=r.slice(0,-1),o.write("\b \b"));break;default:r+=c,o.write("*")}},"onData");n.setRawMode?.(!0),n.resume(),n.setEncoding("utf8"),n.on("data",s)}):""}a(So,"promptHidden");ot();import{createHash as Ms}from"crypto";import{existsSync as on,mkdirSync as Co,readdirSync as Fs,readFileSync as rn,statSync as Js,writeFileSync as Po}from"fs";import{join as ce}from"path";var To=".contextq-sync.json";function Ao(e){return Ms("sha256").update(e,"utf-8").digest("hex")}a(Ao,"sha256");function Eo(e){try{let t=JSON.parse(rn(ce(e,To),"utf-8"));return typeof t.sha256=="string"?t.sha256:null}catch{return null}}a(Eo,"readSidecarHash");function _o(e,t){Po(ce(e,To),JSON.stringify({sha256:Ao(t)})+`
86
+ `,"utf-8")}a(_o,"writeSidecar");function Bs(e,t){let n=new RegExp(`^ {2}${t}: "((?:[^"\\\\]|\\\\.)*)"$`,"m"),o=e.match(n);return o?o[1].replace(/\\(.)/g,"$1"):null}a(Bs,"extractFrontmatterMetadataValue");function Xs(e){return Bs(e,"cluster_key")}a(Xs,"extractClusterKey");function Ys(e){if(!on(e))return[];let t=[];for(let n of Fs(e)){let o=ce(e,n);if(!Js(o).isDirectory())continue;let r=ce(o,"SKILL.md");if(!on(r))continue;let s=rn(r,"utf-8"),i=Xs(s);i&&t.push({folder:n,clusterKey:i})}return t}a(Ys,"scanLocalSkills");function Qs(e,t){return e.replace(/^name: .+$/m,`name: ${t}`)}a(Qs,"withFrontmatterName");async function Ro(e){Co(e.dir,{recursive:!0});let t=Ys(e.dir),n=new Map(t.map(c=>[c.clusterKey,c.folder])),o=new Set(t.map(c=>c.folder)),r=await e.fetchSkills(),s={written:[],updated:[],unchanged:[],orphans:[],keptEdited:[]},i=new Set;for(let c of r){i.add(c.clusterKey);let u=n.get(c.clusterKey);u||(u=o.has(c.slug)?`${c.slug}-c${c.contextId}`:c.slug,o.add(u),n.set(c.clusterKey,u));let d=u===c.slug?c.markdown:Qs(c.markdown,u),f=ce(e.dir,u),p=ce(f,"SKILL.md"),g=on(p)?rn(p,"utf-8"):null;if(g===d){Eo(f)===null&&_o(f,d),s.unchanged.push(u);continue}if(g!==null&&e.force!==!0&&Eo(f)!==Ao(g)){s.keptEdited.push(u);continue}Co(f,{recursive:!0}),Po(p,d,"utf-8"),_o(f,d),s[g===null?"written":"updated"].push(u)}for(let c of t)i.has(c.clusterKey)||s.orphans.push(c.folder);return s}a(Ro,"syncSkills");var G="https://app.contextq.dev";function Wi(e){let t=e.length>0&&!e[0].startsWith("-"),n={command:t?e[0]:"",embedKey:!1,noPlugin:!1,refresh:!1,help:!1,local:!1};for(let o=t?1:0;o<e.length;o++){let r=e[o];switch(r){case"--key":n.key=e[++o];break;case"--url":n.url=e[++o];break;case"--workspace":n.workspace=e[++o];break;case"--agent":n.agent=e[++o];break;case"--embed-key":n.embedKey=!0;break;case"--no-plugin":n.noPlugin=!0;break;case"--refresh":n.refresh=!0;break;case"--local":n.local=!0;break;case"--port":{let s=e[++o]??"",i=/^\d+$/.test(s)?Number(s):NaN;if(!Number.isInteger(i)||i<1||i>65535)throw new Error(`invalid port "${s}"; use a number from 1 to 65535`);n.port=i;break}case"--surface":{let s=e[++o];if(s!=="claude-code"&&s!=="codex"&&s!=="opencode"&&!oe.includes(s))throw new Error(`--surface value "${s}" is invalid -- allowed: claude-code, codex, ${oe.join(", ")}, opencode`);n.surface=s;break}case"-h":case"--help":n.help=!0;break;default:throw new Error(`unknown argument: ${r}`)}}return n}a(Wi,"parseArgs");function Ue(){console.log(`contextq \u2014 one-command installer for ContextQ
83
87
 
84
88
  Usage:
85
89
  npx @contextq/cli login [--url <url>]
@@ -98,15 +102,18 @@ never written.
98
102
  Options:
99
103
  --key <sk_live_...> Pass or rotate the key on the command line (visible in shell
100
104
  history; prefer the prompt or the env var)
101
- --url <url> ContextQ API URL (default: ${V})
105
+ --url <url> ContextQ API URL (default: ${G})
102
106
  --workspace <slug> Workspace slug for the contextq boot hook (default: repo basename,
103
- sanitized). Allowed: ${Ve}. Case is folded to
107
+ sanitized). Allowed: ${nt}. Case is folded to
104
108
  lowercase automatically (workspace slugs are always lowercase).
105
109
  --agent <slug> Agent slug for the contextq boot hook (default: repo basename,
106
- sanitized). Allowed: ${Ve}.
110
+ sanitized). Allowed: ${nt}.
107
111
  --embed-key Write the literal API key into .mcp.json instead of the
108
112
  safer \${CONTEXT_API_KEY} placeholder (opt-in: convenience
109
113
  vs. a secret sitting on disk)
114
+ --refresh Replace ContextQ hook and MCP entries that an older CLI wrote
115
+ with what this CLI writes now. Without it, init only reports
116
+ them as stale and leaves the files untouched.
110
117
  --no-plugin Wire Claude Code through .mcp.json even when a \`claude\` binary
111
118
  (2.1.281+) is on PATH. By default init installs the ContextQ
112
119
  plugin at project scope instead, which carries the MCP server and
@@ -127,16 +134,18 @@ Usage:
127
134
  Pulls this tenant's distilled procedures as standard Agent Skills
128
135
  folders (<dir>/<slug>/SKILL.md). Safe to re-run: an existing folder is
129
136
  matched by its own metadata.cluster_key and rewritten in place, never
130
- duplicated; a folder whose context no longer resolves is reported as an
137
+ duplicated, unless its SKILL.md was edited locally (kept; --force
138
+ overwrites it); a folder whose context no longer resolves is reported as an
131
139
  orphan and left on disk untouched.
132
140
 
133
141
  Options:
142
+ --force Overwrite locally edited SKILL.md files too
134
143
  --workspace <slug> Limit to one workspace's procedures (default: every
135
144
  workspace visible to the tenant)
136
145
  --key <sk_live_...> API key (else CONTEXT_API_KEY/CTQ_API_KEY env, else the
137
146
  credential file written by \`contextq init\`)
138
147
  --url <url> ContextQ API URL (else the credential file, else
139
- CONTEXT_API_URL/CTQ_API_URL env, default: ${V})
148
+ CONTEXT_API_URL/CTQ_API_URL env, default: ${G})
140
149
 
141
150
  Exit codes:
142
151
  0 success (doctor: every step passed)
@@ -144,12 +153,12 @@ Exit codes:
144
153
  no MCP connection wired, or a failed doctor step
145
154
  3 local status: the local server is not running
146
155
  130 interrupted: Ctrl-C at a prompt, or the browser approval was cancelled
147
- `)}a(Oe,"printHelp");function le(e,t){return t.startsWith(`${e}/`)?t.slice(e.length+1):t}a(le,"relative");function O(e){return{created:"[create]",added:"[add] ",wired:"[create]",rotated:"[rotate]",skipped:"[skip] ",verified:"[check]",failed:"[check]",refused:"[refuse]"}[e]??`[${e}]`}a(O,"label");async function Po(e,t){try{let n=await fetch(`${e.replace(/\/$/,"")}/api/workspaces`,{headers:{Authorization:`Bearer ${t}`}});return{ok:n.status===200,status:n.status}}catch(n){return{ok:!1,error:n.message}}}a(Po,"verifyConnection");function en(e){console.log(""),console.log("Approve this machine in your browser:"),console.log(` ${e.verificationUri}`),console.log(` code: ${e.userCode}`),console.log(`Waiting for approval (expires in ${Math.max(1,Math.round(e.expiresInSeconds/60))} min, Ctrl-C to cancel)...`)}a(en,"printDevicePrompt");function tn(e){let t=new AbortController,n=a(()=>t.abort(),"onSigint");return process.once("SIGINT",n),e(t.signal).finally(()=>process.removeListener("SIGINT",n))}a(tn,"withSigintAbort");async function Si(e){let t=N(),n=e.url||t.apiUrl||process.env.CONTEXT_API_URL||V;console.log(`ContextQ login (${n})`);let o=await tn(i=>He({apiUrl:n,signal:i,onPrompt:en})),r=await Po(n,o);if(!r.ok){let i=r.status?`HTTP ${r.status}`:r.error??"unknown error";throw new Error(`could not verify the approved key against ${n} (${i}); nothing was written`)}let s=M({explicitKey:o,ambientVarName:null,ambientKey:"",apiUrl:n});console.log("Approved."),console.log(s.credLine),console.log(`[check] connected to ${n}`)}a(Si,"runLogin");async function Ci(e){let t=process.cwd(),{surfaces:n,root:o}=ge(t,{forcedSurface:e.surface});if(console.log("ContextQ init"),console.log("============="),n.length===1&&n[0]==="desktop"){console.log(`Surface detected: desktop/other (no git repo, .claude/, or .codex/ found at ${t})`),zn(e.url??V);return}console.log(`Surface(s) detected: ${n.join(", ")} (root: ${o})`);let r=N(),s=e.url||r.apiUrl||process.env.CONTEXT_API_URL||V,i=process.env.CONTEXT_API_KEY?"CONTEXT_API_KEY":process.env.CTQ_API_KEY?"CTQ_API_KEY":null,c=i?process.env[i]:"",{decision:l,verify:d}=await kn({explicitKey:e.key,ambientVarName:i,ambientKey:c,apiUrl:s,interactive:!!process.stdin.isTTY,verify:a(m=>Po(s,m),"verify"),prompt:Wn,deviceLogin:a(()=>tn(m=>He({apiUrl:s,signal:m,onPrompt:en})),"deviceLogin")}),u=l.apiKey;if(console.log(l.credLine),!d.ok){let m=d.status?`HTTP ${d.status}`:d.error??"unknown error";console.log(`[warn] could not verify the key against ${s} (${m}); it was kept, re-run init once the server answers`)}let f;e.agent?f=te(e.agent,"--agent"):f=B(Eo(o));let p;if(e.workspace){let m=e.workspace.toLowerCase();p=te(m,"--workspace"),m!==e.workspace&&console.log(`[info] --workspace lowercased to match slug convention: "${e.workspace}" -> "${m}"`)}else p=B(Eo(o));console.log(`[info] agent=${f} workspace=${p}`);let g=process.env.CONTEXTQ_HOOK_RUNNER||J,h,w=!1,x=[],y=!1;if(n.includes("claude-code")){let m=Fn({noPlugin:e.noPlugin,legacyMcpEntry:Mn(o),verifyStatus:d.status});if(!m.delegate)console.log(`[info] ${m.reason}`);else{let S=Qe(o,s,u,V);S.status==="installed"?(w=!0,x.push("claude-code"),console.log(`${O(S.alreadyInstalled?"skipped":"added")} .claude/settings.json (${_} at project scope -- ${S.message})`)):console.log(`[info] plugin install ${S.status}: ${S.reason} -- falling back to .mcp.json wiring`)}if(!w){h=bn(o,s,e.embedKey,u),console.log(`${O(h.action)} ${le(o,h.path)} (${h.detail})`),x.push("claude-code");let S=Rn(o,f,p,g);console.log(`${O(S.action)} ${le(o,S.path)} (${S.detail})`),S.action==="refused"&&(process.exitCode=1)}let b=Gn(o);console.log(`${O(b.action)} ${le(o,b.path)} (${b.detail})`)}if(n.includes("codex")){console.log(""),console.log("[info] Codex hooks -- assumptions this install makes:"),console.log(" - Sandbox/approval: the boot hook makes an outbound HTTP call to the ContextQ"),console.log(" API. Verified empirically (codex-cli 0.146.0, `codex exec --sandbox"),console.log(" workspace-write`, no approval flag): a TRUSTED hook's outbound call is NOT"),console.log(" gated by sandbox_mode/approval_policy the way model-issued shell commands"),console.log(" are -- the real gate is hook TRUST (see the note below), not the sandbox."),console.log(" Once trusted, this should work under any sandbox_mode/approval_policy combo."),console.log(" If it still produces no injected context, `codex doctor` shows current sandbox"),console.log(" + approval state as a starting point for diagnosis."),console.log(" - Native Memories: Codex ships its own Memories feature (off by default). If you"),console.log(" turn it on, set memories.disable_on_external_context = true in config.toml (or"),console.log(" its older alias no_memories_if_mcp_or_web_search) -- otherwise a session that"),console.log(" used MCP tools (including ContextQ's own MCP server) still feeds Codex's native"),console.log(" memory extraction too, duplicating what ContextQ already captures."),console.log("");let m=On(o,f,p,g);if(console.log(`${O(m.action)} ${le(o,m.path)} (${m.detail})`),m.action==="refused"&&(process.exitCode=1),!d.ok)console.log("[info] Codex MCP: not wired -- the key did not verify, and it would be written into .codex/config.toml");else{let b=En(o,s,u);console.log(`${O(b.action)} ${le(o,b.path)} (${b.detail})`),b.action==="refused"?process.exitCode=1:x.push("codex")}}for(let m of ee){if(!n.includes(m))continue;if(!d.ok){console.log(`[info] ${X[m].label}: not wired -- the key did not verify, and it would be written into the host config`);continue}let b=ln(m,{homeDir:Zt(),env:process.env,apiUrl:s,apiKey:u});console.log(`${O(b.action)} ${b.path} (${X[m].label}: ${b.detail})`),b.action==="refused"?process.exitCode=1:x.push(m);let S=Un(m,{homeDir:Zt(),env:process.env,runner:g});console.log(`${O(S.action)} ${S.path} (${X[m].label}: ${S.detail})`),S.action==="refused"?process.exitCode=1:y=!0}if(n.includes("opencode"))if(!d.ok)console.log("[info] OpenCode: not wired -- the key did not verify, and it would be written into the host config");else{let m=un({homeDir:Zt(),env:process.env,apiUrl:s,apiKey:u,major:dn()});console.log(`${O(m.action)} ${m.path} (OpenCode: ${m.detail})`),m.action==="refused"?process.exitCode=1:x.push("opencode")}if(d.ok)console.log(`${O("verified")} connection to ${s} (GET /api/workspaces -> 200)`);else{let m=d.status?`HTTP ${d.status}`:d.error??"unknown error";console.log(`${O("failed")} connection to ${s} (${m}) -- check your API key and --url`),(d.status===401||d.status===403)&&(process.exitCode=1)}if(h&&!e.embedKey&&h.action!=="skipped"&&(console.log(""),console.log("NOTE: .mcp.json references ${CONTEXT_API_KEY}. When that variable is unset, the"),console.log(`current @contextq/mcp reads the key from ${A()} instead.`),console.log('If `npx @contextq/mcp` still logs "Auth: NONE", export it before starting Claude Code:'),console.log(" export CONTEXT_API_KEY=<your key>"),console.log("...or re-run with --embed-key to write the literal key into .mcp.json instead.")),y){let m=jn(o,p,f);console.log(`${O(m.action)} ${le(o,m.path)} (${m.detail})`)}console.log("");let v={"claude-code":"a new Claude Code session",codex:"a new `codex` run",gemini:"a new `gemini` session",cursor:"a new Cursor or `cursor-agent` session",copilot:"a new `copilot` session",opencode:"a new `opencode` session"},k=n.filter(m=>m!=="desktop"&&x.includes(m)).map(m=>v[m]).join(" / start ");k?console.log(`Done. Open ${k} in this repo to pick up the wiring.`):(console.log("Nothing was wired: no surface got a ContextQ MCP connection. Fix what is reported above and re-run."),process.exitCode=1),w?(console.log(""),console.log("Next: in that Claude Code session, run /contextq:init to bind this repo to a workspace"),console.log("and project. Until it writes .contextq/config.json, the plugin's MCP tools work but its"),console.log("SessionStart/PreCompact/SessionEnd hooks stay silent and say so.")):n.includes("claude-code")&&(console.log(""),console.log("Claude Code 2.1.281+ can carry all of this as a plugin instead -- MCP server and"),console.log("lifecycle hooks, key in Claude Code's own store. Re-run init without --no-plugin after"),console.log("removing the contextq entry from .mcp.json, rather than installing the plugin on top:"),console.log("both would register the same MCP server. See https://app.contextq.dev/install.md."))}a(Ci,"runInit");function _i(e){let t=[],n={};for(let o=0;o<e.length;o++){let r=e[o];switch(r){case"--workspace":n.workspace=e[++o];break;case"--key":n.key=e[++o];break;case"--url":n.url=e[++o];break;default:if(r.startsWith("-"))throw new Error(`unknown argument: ${r}`);t.push(r)}}if(t.length===0)throw new Error("missing <dir> argument -- usage: contextq skills sync <dir>");if(t.length>1)throw new Error(`unexpected extra argument: ${t[1]}`);return{...n,dir:t[0]}}a(_i,"parseSkillsSyncArgs");function Ei(e){let t=N(),n=e.url||t.apiUrl||process.env.CONTEXT_API_URL||process.env.CTQ_API_URL||V,o=e.key||process.env.CONTEXT_API_KEY||process.env.CTQ_API_KEY||t.apiKey;if(!o)throw new Error("no API key available -- pass --key, set CONTEXT_API_KEY, or run `contextq init` first to write the credential file");return{apiUrl:n,apiKey:o}}a(Ei,"resolveApiCreds");async function Pi(e,t,n){let o=new URL("/api/agent/skills/export",e.replace(/\/$/,"")+"/");o.searchParams.set("format","json"),n&&o.searchParams.set("workspace",n);let r=await fetch(o,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)throw new Error(`GET ${o.pathname}${o.search} -> HTTP ${r.status}`);return(await r.json()).skills}a(Pi,"fetchSkillsFromApi");async function Ti(e){console.log("contextq skills sync"),console.log("=====================");let{apiUrl:t,apiKey:n}=Ei(e);console.log(`[info] syncing to ${e.dir}${e.workspace?` (workspace: ${e.workspace})`:""}`);let o=await to({dir:e.dir,fetchSkills:a(()=>Pi(t,n,e.workspace),"fetchSkills")});for(let r of o.written)console.log(`${O("created")} ${r}/SKILL.md`);for(let r of o.updated)console.log(`${O("added")} ${r}/SKILL.md (rewritten in place)`);if(console.log(`[info] ${o.written.length} written, ${o.updated.length} updated, ${o.unchanged.length} unchanged`),o.orphans.length>0){console.log(`[warn] ${o.orphans.length} orphaned folder(s) -- left on disk, not deleted:`);for(let r of o.orphans)console.log(` ${r}/ (its context no longer resolves)`)}}a(Ti,"runSkillsSync");function Ai(e){let t="claude",n=!1,o,r;for(let s=0;s<e.length;s++){let i=e[s],c=e[s+1];if(i==="--format"&&c){if(!ye.includes(c))throw new Error(`--format must be one of: ${ye.join(", ")}`);t=c,s++}else if(i==="--agent"&&c)o=c,s++;else if(i==="--workspace"&&c)r=c,s++;else if(i==="--require-config")n=!0;else throw new Error(`unknown "hook boot" argument "${i}"`)}return{format:t,agent:o,workspace:r,requireConfig:n}}a(Ai,"parseHookBootArgs");async function Ri(e){try{let t=Ai(e),n=await Vn({cwd:process.env.CLAUDE_PROJECT_DIR||process.cwd(),env:process.env,stdinText:await Ht(),...t});n&&process.stdout.write(`${n}
148
- `)}catch(t){console.error(`contextq hook boot: ${t.message}`)}}a(Ri,"runHookBootCommand");function Oi(e){let t="copilot",n="end",o=!1;for(let r=0;r<e.length;r++){let s=e[r],i=e[r+1];if(s==="--format"&&i){if(!ye.includes(i))throw new Error(`--format must be one of: ${ye.join(", ")}`);t=i,r++}else if(s==="--reason"&&i){if(!Ut.includes(i))throw new Error(`--reason must be one of: ${Ut.join(", ")}`);n=i,r++}else if(s==="--require-config")o=!0;else throw new Error(`unknown "hook snapshot" argument "${s}"`)}return{format:t,reason:n,requireConfig:o}}a(Oi,"parseHookSnapshotArgs");async function Ii(e){try{let t=Oi(e),n=await Xn({cwd:process.cwd(),env:process.env,stdinText:await Ht(),...t});n.outcome==="failed"&&console.error(`contextq hook snapshot: ${n.detail}; this session's snapshot was not saved`)}catch(t){console.error(`contextq hook snapshot: ${t.message}`)}}a(Ii,"runHookSnapshotCommand");async function Ni(e){let t={url:V,restart:!1,leaveDenied:!1,abort:!1,keyStdin:!1,ping:void 0};for(let s=0;s<e.length;s++){let i=e[s];if(i==="--url")t.url=e[++s]??"";else if(i==="--restart")t.restart=!0;else if(i==="--leave-denied")t.leaveDenied=!0;else if(i==="--abort")t.abort=!0;else if(i==="--key-stdin")t.keyStdin=!0;else if(i==="--ping")t.ping=!0;else if(i==="--no-ping")t.ping=!1;else{console.error(`contextq: unknown option "${i}" for attach; usage: contextq attach [--url <url>] [--restart] [--leave-denied] [--abort] [--key-stdin] [--ping|--no-ping]`),process.exitCode=1;return}}let n=await Promise.resolve().then(()=>($o(),bo)),{gitRoot:o}=await Promise.resolve().then(()=>(se(),_e)),r=null;try{r=o(process.cwd())}catch{r=null}try{if(t.abort){console.log(await n.abortAttach({cloudUrl:t.url,repoRoot:r}));return}console.log(`ContextQ attach -> ${t.url}`),await n.attachTransfer({cloudUrl:t.url,restart:t.restart,leaveDenied:t.leaveDenied,login:t.keyStdin?async()=>{let i=[];for await(let l of process.stdin)i.push(l);let c=Buffer.concat(i).toString("utf8").trim();if(!I.test(c))throw new Error("--key-stdin expects one sk_live_ key on stdin");return c}:()=>tn(i=>He({apiUrl:t.url,signal:i,onPrompt:en}))});let s=await n.attachSwitch({cloudUrl:t.url,repoRoot:r,pingConsent:t.ping});console.log(""),console.log(`Attached: ${Object.keys(s.acked).length} note(s) now live in your ContextQ account at ${t.url}.`),console.log("The local copy and its keys stay on this machine, read-only. Open a new Claude Code session to use the cloud memory, board and handoffs.")}catch(s){console.error(`contextq: ${s.message}`),process.exitCode=1}}a(Ni,"runAttachCommand");async function Di(e){let t=!1,n;for(let i=0;i<e.length;i++){let c=e[i];if(c==="--json")t=!0;else if(c==="--workspace"&&e[i+1])n=e[++i];else if(c==="-h"||c==="--help"){console.log(`Usage:
156
+ `)}a(Ue,"printHelp");function ge(e,t){return t.startsWith(`${e}/`)?t.slice(e.length+1):t}a(ge,"relative");function I(e){return{created:"[create]",added:"[add] ",wired:"[create]",rotated:"[rotate]",skipped:"[skip] ",verified:"[check]",failed:"[check]",refused:"[refuse]",stale:"[stale]",updated:"[update]"}[e]??`[${e}]`}a(I,"label");async function sr(e,t){try{let n=await fetch(`${e.replace(/\/$/,"")}/api/workspaces`,{headers:{Authorization:`Bearer ${t}`}});return{ok:n.status===200,status:n.status}}catch(n){return{ok:!1,error:n.message}}}a(sr,"verifyConnection");function yn(e){console.log(""),console.log("Approve this machine in your browser:"),console.log(` ${e.verificationUri}`),console.log(` code: ${e.userCode}`),console.log(`Waiting for approval (expires in ${Math.max(1,Math.round(e.expiresInSeconds/60))} min, Ctrl-C to cancel)...`)}a(yn,"printDevicePrompt");function kn(e){let t=new AbortController,n=a(()=>t.abort(),"onSigint");return process.once("SIGINT",n),e(t.signal).finally(()=>process.removeListener("SIGINT",n))}a(kn,"withSigintAbort");async function Zi(e){let t=H(),n=e.url||t.apiUrl||process.env.CONTEXT_API_URL||G;console.log(`ContextQ login (${n})`);let o=await kn(i=>Be({apiUrl:n,signal:i,onPrompt:yn})),r=await sr(n,o);if(!r.ok){let i=r.status?`HTTP ${r.status}`:r.error??"unknown error";throw new Error(`could not verify the approved key against ${n} (${i}); nothing was written`)}let s=B({explicitKey:o,ambientVarName:null,ambientKey:"",apiUrl:n});console.log("Approved."),console.log(s.credLine),console.log(`[check] connected to ${n}`)}a(Zi,"runLogin");async function ea(e){let t=process.cwd(),{surfaces:n,root:o}=ke(t,{forcedSurface:e.surface});if(console.log("ContextQ init"),console.log("============="),n.length===1&&n[0]==="desktop"){console.log(`Surface detected: desktop/other (no git repo, .claude/, or .codex/ found at ${t})`),$o(e.url??G);return}console.log(`Surface(s) detected: ${n.join(", ")} (root: ${o})`);let r=H(),s=e.url||r.apiUrl||process.env.CONTEXT_API_URL||G,i=process.env.CONTEXT_API_KEY?"CONTEXT_API_KEY":process.env.CTQ_API_KEY?"CTQ_API_KEY":null,c=i?process.env[i]:"",{decision:l,verify:u}=await Hn({explicitKey:e.key,ambientVarName:i,ambientKey:c,apiUrl:s,interactive:!!process.stdin.isTTY,verify:a(w=>sr(s,w),"verify"),prompt:So,deviceLogin:a(()=>kn(w=>Be({apiUrl:s,signal:w,onPrompt:yn})),"deviceLogin")}),d=l.apiKey;if(console.log(l.credLine),!u.ok){let w=u.status?`HTTP ${u.status}`:u.error??"unknown error";console.log(`[warn] could not verify the key against ${s} (${w}); it was kept, re-run init once the server answers`)}let f;e.agent?f=ae(e.agent,"--agent"):f=Y(rr(o));let p;if(e.workspace){let w=e.workspace.toLowerCase();p=ae(w,"--workspace"),w!==e.workspace&&console.log(`[info] --workspace lowercased to match slug convention: "${e.workspace}" -> "${w}"`)}else p=Y(rr(o));console.log(`[info] agent=${f} workspace=${p}`);let g=process.env.CONTEXTQ_HOOK_RUNNER||D,h,m=!1,k=[],y=!1;if(n.includes("claude-code")){let w=ho({noPlugin:e.noPlugin,legacyMcpEntry:go(o),verifyStatus:u.status});if(!w.delegate)console.log(`[info] ${w.reason}`);else{let C=tt(o,s,d,G);C.status==="installed"?(m=!0,k.push("claude-code"),console.log(`${I(C.alreadyInstalled?"skipped":"added")} .claude/settings.json (${E} at project scope -- ${C.message})`)):console.log(`[info] plugin install ${C.status}: ${C.reason} -- falling back to .mcp.json wiring`)}if(!m){h=Mn(o,s,e.embedKey,d,{refresh:e.refresh}),console.log(`${I(h.action)} ${ge(o,h.path)} (${h.detail})`),k.push("claude-code");let C=zn(o,f,p,g,{refresh:e.refresh});console.log(`${I(C.action)} ${ge(o,C.path)} (${C.detail})`),C.action==="refused"&&(process.exitCode=1)}let b=bo(o);console.log(`${I(b.action)} ${ge(o,b.path)} (${b.detail})`)}if(n.includes("codex")){console.log(""),console.log("[info] Codex hooks -- assumptions this install makes:"),console.log(" - Sandbox/approval: the boot hook makes an outbound HTTP call to the ContextQ"),console.log(" API. Verified empirically (codex-cli 0.146.0, `codex exec --sandbox"),console.log(" workspace-write`, no approval flag): a TRUSTED hook's outbound call is NOT"),console.log(" gated by sandbox_mode/approval_policy the way model-issued shell commands"),console.log(" are -- the real gate is hook TRUST (see the note below), not the sandbox."),console.log(" Once trusted, this should work under any sandbox_mode/approval_policy combo."),console.log(" If it still produces no injected context, `codex doctor` shows current sandbox"),console.log(" + approval state as a starting point for diagnosis."),console.log(" - Native Memories: Codex ships its own Memories feature (off by default). If you"),console.log(" turn it on, set memories.disable_on_external_context = true in config.toml (or"),console.log(" its older alias no_memories_if_mcp_or_web_search) -- otherwise a session that"),console.log(" used MCP tools (including ContextQ's own MCP server) still feeds Codex's native"),console.log(" memory extraction too, duplicating what ContextQ already captures."),console.log("");let w=Wn(o,f,p,g,{refresh:e.refresh});if(console.log(`${I(w.action)} ${ge(o,w.path)} (${w.detail})`),w.action==="refused"&&(process.exitCode=1),!u.ok)console.log("[info] Codex MCP: not wired -- the key did not verify, and it would be written into .codex/config.toml");else{let b=Xn(o,s,d,{refresh:e.refresh});console.log(`${I(b.action)} ${ge(o,b.path)} (${b.detail})`),b.action==="refused"?process.exitCode=1:k.push("codex")}}for(let w of oe){if(!n.includes(w))continue;if(!u.ok){console.log(`[info] ${M[w].label}: not wired -- the key did not verify, and it would be written into the host config`);continue}let b=_n(w,{homeDir:wn(),env:process.env,apiUrl:s,apiKey:d,refresh:e.refresh});console.log(`${I(b.action)} ${b.path} (${M[w].label}: ${b.detail})`),b.action==="refused"?process.exitCode=1:k.push(w);let C=lo(w,{homeDir:wn(),env:process.env,runner:g,refresh:e.refresh});console.log(`${I(C.action)} ${C.path} (${M[w].label}: ${C.detail})`),C.action==="refused"?process.exitCode=1:y=!0}if(n.includes("opencode"))if(!u.ok)console.log("[info] OpenCode: not wired -- the key did not verify, and it would be written into the host config");else{let w=Tn({homeDir:wn(),env:process.env,apiUrl:s,apiKey:d,major:Pn()});console.log(`${I(w.action)} ${w.path} (OpenCode: ${w.detail})`),w.action==="refused"?process.exitCode=1:k.push("opencode")}if(u.ok)console.log(`${I("verified")} connection to ${s} (GET /api/workspaces -> 200)`);else{let w=u.status?`HTTP ${u.status}`:u.error??"unknown error";console.log(`${I("failed")} connection to ${s} (${w}) -- check your API key and --url`),(u.status===401||u.status===403)&&(process.exitCode=1)}if(h&&!e.embedKey&&(h.action==="created"||h.action==="added"||h.action==="updated")&&(console.log(""),console.log("NOTE: .mcp.json references ${CONTEXT_API_KEY}. When that variable is unset, the"),console.log(`current @contextq/mcp reads the key from ${A()} instead.`),console.log('If `npx @contextq/mcp` still logs "Auth: NONE", export it before starting Claude Code:'),console.log(" export CONTEXT_API_KEY=<your key>"),console.log("...or re-run with --embed-key to write the literal key into .mcp.json instead.")),y){let w=uo(o,p,f);console.log(`${I(w.action)} ${ge(o,w.path)} (${w.detail})`)}console.log("");let v={"claude-code":"a new Claude Code session",codex:"a new `codex` run",gemini:"a new `gemini` session",cursor:"a new Cursor or `cursor-agent` session",copilot:"a new `copilot` session",opencode:"a new `opencode` session"},x=n.filter(w=>w!=="desktop"&&k.includes(w)).map(w=>v[w]).join(" / start ");x?console.log(`Done. Open ${x} in this repo to pick up the wiring.`):(console.log("Nothing was wired: no surface got a ContextQ MCP connection. Fix what is reported above and re-run."),process.exitCode=1),m?(console.log(""),console.log("Next: in that Claude Code session, run /contextq:init to bind this repo to a workspace"),console.log("and project. Until it writes .contextq/config.json, the plugin's MCP tools work but its"),console.log("SessionStart/PreCompact/SessionEnd hooks stay silent and say so.")):n.includes("claude-code")&&(console.log(""),console.log("Claude Code 2.1.281+ can carry all of this as a plugin instead -- MCP server and"),console.log("lifecycle hooks, key in Claude Code's own store. Re-run init without --no-plugin after"),console.log("removing the contextq entry from .mcp.json, rather than installing the plugin on top:"),console.log("both would register the same MCP server. See https://app.contextq.dev/install.md."))}a(ea,"runInit");function ta(e){let t=[],n={};for(let o=0;o<e.length;o++){let r=e[o];switch(r){case"--workspace":n.workspace=e[++o];break;case"--key":n.key=e[++o];break;case"--url":n.url=e[++o];break;case"--force":n.force=!0;break;default:if(r.startsWith("-"))throw new Error(`unknown argument: ${r}`);t.push(r)}}if(t.length===0)throw new Error("missing <dir> argument -- usage: contextq skills sync <dir>");if(t.length>1)throw new Error(`unexpected extra argument: ${t[1]}`);return{...n,dir:t[0]}}a(ta,"parseSkillsSyncArgs");function na(e){let t=H(),n=e.url||t.apiUrl||process.env.CONTEXT_API_URL||process.env.CTQ_API_URL||G,o=e.key||process.env.CONTEXT_API_KEY||process.env.CTQ_API_KEY||t.apiKey;if(!o)throw new Error("no API key available -- pass --key, set CONTEXT_API_KEY, or run `contextq init` first to write the credential file");return{apiUrl:n,apiKey:o}}a(na,"resolveApiCreds");async function oa(e,t,n){let o=new URL("/api/agent/skills/export",e.replace(/\/$/,"")+"/");o.searchParams.set("format","json"),n&&o.searchParams.set("workspace",n);let r=await fetch(o,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)throw new Error(`GET ${o.pathname}${o.search} -> HTTP ${r.status}`);return(await r.json()).skills}a(oa,"fetchSkillsFromApi");async function ra(e){console.log("contextq skills sync"),console.log("=====================");let{apiUrl:t,apiKey:n}=na(e);console.log(`[info] syncing to ${e.dir}${e.workspace?` (workspace: ${e.workspace})`:""}`);let o=await Ro({dir:e.dir,fetchSkills:a(()=>oa(t,n,e.workspace),"fetchSkills"),force:e.force});for(let r of o.written)console.log(`${I("created")} ${r}/SKILL.md`);for(let r of o.updated)console.log(`${I("added")} ${r}/SKILL.md (rewritten in place)`);if(console.log(`[info] ${o.written.length} written, ${o.updated.length} updated, ${o.unchanged.length} unchanged`),o.keptEdited.length>0){console.log(`[warn] ${o.keptEdited.length} folder(s) edited locally, kept; --force overwrites:`);for(let r of o.keptEdited)console.log(` ${r}/SKILL.md`)}if(o.orphans.length>0){console.log(`[warn] ${o.orphans.length} orphaned folder(s) -- left on disk, not deleted:`);for(let r of o.orphans)console.log(` ${r}/ (its context no longer resolves)`)}}a(ra,"runSkillsSync");function sa(e){let t="claude",n=!1,o,r;for(let s=0;s<e.length;s++){let i=e[s],c=e[s+1];if(i==="--format"&&c){if(!Ce.includes(c))throw new Error(`--format must be one of: ${Ce.join(", ")}`);t=c,s++}else if(i==="--agent"&&c)o=c,s++;else if(i==="--workspace"&&c)r=c,s++;else if(i==="--require-config")n=!0;else throw new Error(`unknown "hook boot" argument "${i}"`)}return{format:t,agent:o,workspace:r,requireConfig:n}}a(sa,"parseHookBootArgs");async function ia(e){try{let t=sa(e),n=await ko({cwd:process.env.CLAUDE_PROJECT_DIR||process.cwd(),env:process.env,stdinText:await en(),...t});n&&process.stdout.write(`${n}
157
+ `)}catch(t){console.error(`contextq hook boot: ${t.message}`)}}a(ia,"runHookBootCommand");function aa(e){let t="copilot",n="end",o=!1;for(let r=0;r<e.length;r++){let s=e[r],i=e[r+1];if(s==="--format"&&i){if(!Ce.includes(i))throw new Error(`--format must be one of: ${Ce.join(", ")}`);t=i,r++}else if(s==="--reason"&&i){if(!tn.includes(i))throw new Error(`--reason must be one of: ${tn.join(", ")}`);n=i,r++}else if(s==="--require-config")o=!0;else throw new Error(`unknown "hook snapshot" argument "${s}"`)}return{format:t,reason:n,requireConfig:o}}a(aa,"parseHookSnapshotArgs");async function ca(e){try{let t=aa(e),n=await xo({cwd:process.cwd(),env:process.env,stdinText:await en(),...t});n.outcome==="failed"&&console.error(`contextq hook snapshot: ${n.detail}; this session's snapshot was not saved`)}catch(t){console.error(`contextq hook snapshot: ${t.message}`)}}a(ca,"runHookSnapshotCommand");async function la(e){let t={url:G,restart:!1,leaveDenied:!1,abort:!1,keyStdin:!1,ping:void 0};for(let s=0;s<e.length;s++){let i=e[s];if(i==="--url")t.url=e[++s]??"";else if(i==="--restart")t.restart=!0;else if(i==="--leave-denied")t.leaveDenied=!0;else if(i==="--abort")t.abort=!0;else if(i==="--key-stdin")t.keyStdin=!0;else if(i==="--ping")t.ping=!0;else if(i==="--no-ping")t.ping=!1;else{console.error(`contextq: unknown option "${i}" for attach; usage: contextq attach [--url <url>] [--restart] [--leave-denied] [--abort] [--key-stdin] [--ping|--no-ping]`),process.exitCode=1;return}}let n=await Promise.resolve().then(()=>(Wo(),zo)),{gitRoot:o}=await Promise.resolve().then(()=>(pe(),Ie)),r=null;try{r=o(process.cwd())}catch{r=null}try{if(t.abort){console.log(await n.abortAttach({cloudUrl:t.url,repoRoot:r}));return}console.log(`ContextQ attach -> ${t.url}`),await n.attachTransfer({cloudUrl:t.url,restart:t.restart,leaveDenied:t.leaveDenied,login:t.keyStdin?async()=>{let i=[];for await(let l of process.stdin)i.push(l);let c=Buffer.concat(i).toString("utf8").trim();if(!N.test(c))throw new Error("--key-stdin expects one sk_live_ key on stdin");return c}:()=>kn(i=>Be({apiUrl:t.url,signal:i,onPrompt:yn}))});let s=await n.attachSwitch({cloudUrl:t.url,repoRoot:r,pingConsent:t.ping});console.log(""),console.log(`Attached: ${Object.keys(s.acked).length} note(s) now live in your ContextQ account at ${t.url}.`),console.log("The local copy and its keys stay on this machine, read-only. Open a new Claude Code session to use the cloud memory, board and handoffs.")}catch(s){console.error(`contextq: ${s.message}`),process.exitCode=1}}a(la,"runAttachCommand");async function da(e){let t=!1,n;for(let i=0;i<e.length;i++){let c=e[i];if(c==="--json")t=!0;else if(c==="--workspace"&&e[i+1])n=e[++i];else if(c==="-h"||c==="--help"){console.log(`Usage:
149
158
  npx @contextq/cli doctor [--json] [--workspace <slug>]
150
159
 
151
160
  Checks, for every detected and wired host, that the configured API answers,
152
161
  the key is accepted, the bound workspace reads, the host MCP entry points at
153
162
  the same endpoint, the boot hook is wired, and a probe note round-trips.
154
163
  Prints one PASS/FAIL line per step (SKIP when a detected host is not wired)
155
- and exits 0 only when everything passes. --json prints the same as JSON.`);return}else{console.error(`contextq: unknown option "${c}" for "doctor"; usage: contextq doctor [--json] [--workspace <slug>]`),process.exitCode=1;return}}let{runDoctor:o,formatHuman:r}=await Promise.resolve().then(()=>(_o(),Co)),s=await o({workspace:n});if(t)console.log(JSON.stringify({ok:s.ok,mode:s.mode,apiUrl:s.apiUrl,workspace:s.workspace,results:s.lines},null,2));else for(let i of r(s))console.log(i);s.ok||(process.exitCode=1)}a(Di,"runDoctorCommand");async function qi(e){let t=await Promise.resolve().then(()=>(Ce(),uo)),n=e[0],o={};for(let r=1;r<e.length;r++)if(e[r]==="--port"){let s=e[++r]??"",i=/^\d+$/.test(s)?Number(s):NaN;if(!Number.isInteger(i)||i<1||i>65535){console.error(`contextq: invalid port "${s}"; use a number from 1 to 65535`),process.exitCode=1;return}o.port=i}else if(e[r]==="--state-dir")o.stateDir=e[++r];else{console.error(`contextq: unknown option "${e[r]}" for "local ${n??""}"`),process.exitCode=1;return}try{if(n==="start"){let r=await t.start(o);r.warning&&console.log(`[warn] ${r.warning}`),console.log(`${r.started?"[start]":"[skip] "} ContextQ local server ${r.started?"started":"already running"} at ${r.url} (pid ${r.pid}, edge ${r.edgeVersion})`),console.log(`[info] state: ${r.stateDir}`)}else if(n==="status"){let r=await t.status(o);r.running?console.log(`running at ${r.url} (pid ${r.pid}, edge ${r.edgeVersion}${r.edgeVersion!==r.pinnedVersion?`, pinned ${r.pinnedVersion}`:""})`):(console.log(`not running: ${r.note??"not started"}`),process.exitCode=3),console.log(`state: ${r.stateDir}`)}else if(n==="stop"){let r=await t.stop(o);console.log(r.stopped?`[stop] ${r.note}`:`[skip] ${r.note}`)}else console.error("contextq: usage: contextq local start|status|stop [--port <n>] [--state-dir <dir>]"),process.exitCode=1}catch(r){console.error(`contextq: ${r.message}`),process.exitCode=1}}a(qi,"runLocalCommand");function Hi(e){let t=["CTQ_API_KEY","CTQ_API_URL"].filter(n=>{let o=n==="CTQ_API_KEY"?"CONTEXT_API_KEY":"CONTEXT_API_URL";return!!e[n]&&!e[o]});return t.length===0?null:`contextq: ${t.join(" and ")} ${t.length===1?"is":"are"} deprecated; set CONTEXT_API_KEY / CONTEXT_API_URL instead (the old names still work for now).`}a(Hi,"deprecatedEnvNotice");async function Ui(){let e=process.argv.slice(2);if(e[0]==="hook"){if(e[1]==="boot"){await Ri(e.slice(2));return}if(e[1]==="snapshot"){await Ii(e.slice(2));return}console.error(`contextq: unknown "hook" subcommand "${e[1]??""}" -- expected "boot" or "snapshot"`),process.exitCode=1;return}let t=Hi(process.env);if(t&&console.error(t),e[0]==="attach"){await Ni(e.slice(1));return}if(e[0]==="local"){await qi(e.slice(1));return}if(e[0]==="doctor"){await Di(e.slice(1));return}if(e[0]==="skills"){if(e[1]==="-h"||e[1]==="--help"){Oe();return}if(e[1]!=="sync"){console.error(`contextq: unknown "skills" subcommand "${e[1]??""}" -- expected "sync"`),Oe(),process.exitCode=1;return}try{let o=_i(e.slice(2));await Ti(o)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=1}return}let n;try{n=$i(e)}catch(o){console.error(`contextq: ${o.message}`),Oe(),process.exitCode=1;return}if(n.help||!n.command){Oe();return}if(n.command==="login"){try{await Si(n)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=o instanceof E&&o.code==="cancelled"?130:1}return}if(n.command!=="init"){console.error(`contextq: unknown command "${n.command}"`),Oe(),process.exitCode=1;return}try{if(n.local){if(n.key||n.url||n.embedKey||n.noPlugin||n.surface)throw new Error("--local takes only --workspace, --agent and --port: it needs no key, URL or surface");let{runLocalInit:o}=await Promise.resolve().then(()=>(se(),_e));await o({cwd:process.cwd(),workspace:n.workspace,agent:n.agent,runtime:n.port?{port:n.port}:void 0});return}await Ci(n)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=1}}a(Ui,"main");Ui();export{Hi as deprecatedEnvNotice};
164
+ and exits 0 only when everything passes. --json prints the same as JSON.`);return}else{console.error(`contextq: unknown option "${c}" for "doctor"; usage: contextq doctor [--json] [--workspace <slug>]`),process.exitCode=1;return}}let{runDoctor:o,formatHuman:r}=await Promise.resolve().then(()=>(or(),nr)),s=await o({workspace:n});if(t)console.log(JSON.stringify({ok:s.ok,mode:s.mode,apiUrl:s.apiUrl,workspace:s.workspace,results:s.lines},null,2));else for(let i of r(s))console.log(i);s.ok||(process.exitCode=1)}a(da,"runDoctorCommand");async function ua(e){let t=await Promise.resolve().then(()=>(Oe(),Lo)),n=e[0],o={};for(let r=1;r<e.length;r++)if(e[r]==="--port"){let s=e[++r]??"",i=/^\d+$/.test(s)?Number(s):NaN;if(!Number.isInteger(i)||i<1||i>65535){console.error(`contextq: invalid port "${s}"; use a number from 1 to 65535`),process.exitCode=1;return}o.port=i}else if(e[r]==="--state-dir")o.stateDir=e[++r];else{console.error(`contextq: unknown option "${e[r]}" for "local ${n??""}"`),process.exitCode=1;return}try{if(n==="start"){let r=await t.start(o);r.warning&&console.log(`[warn] ${r.warning}`),console.log(`${r.started?"[start]":"[skip] "} ContextQ local server ${r.started?"started":"already running"} at ${r.url} (pid ${r.pid}, edge ${r.edgeVersion})`),console.log(`[info] state: ${r.stateDir}`)}else if(n==="status"){let r=await t.status(o);r.running?console.log(`running at ${r.url} (pid ${r.pid}, edge ${r.edgeVersion}${r.edgeVersion!==r.pinnedVersion?`, pinned ${r.pinnedVersion}`:""})`):(console.log(`not running: ${r.note??"not started"}`),process.exitCode=3),console.log(`state: ${r.stateDir}`)}else if(n==="stop"){let r=await t.stop(o);console.log(r.stopped?`[stop] ${r.note}`:`[skip] ${r.note}`)}else console.error("contextq: usage: contextq local start|status|stop [--port <n>] [--state-dir <dir>]"),process.exitCode=1}catch(r){console.error(`contextq: ${r.message}`),process.exitCode=1}}a(ua,"runLocalCommand");function pa(e){let t=["CTQ_API_KEY","CTQ_API_URL"].filter(n=>{let o=n==="CTQ_API_KEY"?"CONTEXT_API_KEY":"CONTEXT_API_URL";return!!e[n]&&!e[o]});return t.length===0?null:`contextq: ${t.join(" and ")} ${t.length===1?"is":"are"} deprecated; set CONTEXT_API_KEY / CONTEXT_API_URL instead (the old names still work for now).`}a(pa,"deprecatedEnvNotice");async function fa(){let e=process.argv.slice(2);if(e[0]==="hook"){if(e[1]==="boot"){await ia(e.slice(2));return}if(e[1]==="snapshot"){await ca(e.slice(2));return}console.error(`contextq: unknown "hook" subcommand "${e[1]??""}" -- expected "boot" or "snapshot"`),process.exitCode=1;return}let t=pa(process.env);if(t&&console.error(t),e[0]==="attach"){await la(e.slice(1));return}if(e[0]==="local"){await ua(e.slice(1));return}if(e[0]==="doctor"){await da(e.slice(1));return}if(e[0]==="skills"){if(e[1]==="-h"||e[1]==="--help"){Ue();return}if(e[1]!=="sync"){console.error(`contextq: unknown "skills" subcommand "${e[1]??""}" -- expected "sync"`),Ue(),process.exitCode=1;return}try{let o=ta(e.slice(2));await ra(o)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=1}return}let n;try{n=Wi(e)}catch(o){console.error(`contextq: ${o.message}`),Ue(),process.exitCode=1;return}if(n.help||!n.command){Ue();return}if(n.command==="login"){try{await Zi(n)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=o instanceof _&&o.code==="cancelled"?130:1}return}if(n.command!=="init"){console.error(`contextq: unknown command "${n.command}"`),Ue(),process.exitCode=1;return}try{if(n.local){if(n.key||n.url||n.embedKey||n.noPlugin||n.refresh||n.surface)throw new Error("--local takes only --workspace, --agent and --port: it needs no key, URL or surface");let{runLocalInit:o}=await Promise.resolve().then(()=>(pe(),Ie));await o({cwd:process.cwd(),workspace:n.workspace,agent:n.agent,runtime:n.port?{port:n.port}:void 0});return}await ea(n)}catch(o){console.error(`contextq: ${o.message}`),process.exitCode=1}}a(fa,"main");fa();export{pa as deprecatedEnvNotice};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextq/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "One-command installer for ContextQ -- wires the MCP server, the contextq SessionStart boot hook, and the task manifest into a project (or prints a manual connect guide for Desktop/Cowork).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,7 @@
13
13
  "scripts": {
14
14
  "dev": "tsx src/index.ts",
15
15
  "build": "tsc --noEmit && tsup",
16
+ "pretest": "node scripts/ensure-edge.mjs",
16
17
  "test": "tsx --test 'src/**/*.test.ts'",
17
18
  "prepublishOnly": "npm run build"
18
19
  },