@thinkermd/cli 0.1.17 → 0.1.18

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/tk.js +1 -1
  2. package/package.json +1 -1
package/dist/tk.js CHANGED
@@ -100,7 +100,7 @@ This appears to be the first generation. Use 'tk init' to create initial structu
100
100
  <p>You can now close this window and return to your terminal.</p>
101
101
  </div>
102
102
  </body>
103
- </html>`),c(),r({token:u,userId:g,email:f})):(l.writeHead(400,{"Content-Type":"text/plain"}),l.end("Missing required parameters"))}else l.writeHead(404,{"Content-Type":"text/plain"}),l.end("Not found")}),i.listen(e)})}async function io(e,t){try{let o=await fetch(`${e}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return o.ok?{valid:!0,userId:(await o.json()).userId}:o.status===401?{valid:!1,error:"Invalid or expired token"}:{valid:!1,error:`API error: ${o.status} ${o.statusText}`}}catch(o){return{valid:!1,error:`Connection failed: ${o}`}}}async function fe(e){let t=ue(),o=e.apiUrl||J;if(e.token){t.start("Validating token...");let n=await io(o,e.token);n.valid||(t.fail("Authentication failed"),console.error(b.red(`Error: ${n.error}`)),process.exit(1));let i={apiToken:e.token,apiUrl:o,userId:n.userId};await nt(i),t.succeed("Authenticated successfully"),console.log(),console.log(`Config saved to: ${b.cyan(L)}`);return}let r=await $();if(r.apiToken){console.log(b.green("You are already logged in.")),console.log(`User ID: ${b.cyan(r.userId||"unknown")}`),console.log(),console.log("To re-authenticate, run:"),console.log(b.cyan(" tk login --token NEW_TOKEN")),console.log(),console.log("Or to login with a different account:"),console.log(b.cyan(" tk logout && tk login"));return}console.log(b.bold("ThinkerMD Login")),console.log(),t.start("Setting up authentication...");try{let n=await ge(),i=to(16).toString("hex"),s=`${o}/cli/auth?port=${n}&state=${i}`;t.succeed("Ready for authentication"),console.log(),console.log("Opening browser for authentication..."),console.log(),console.log("If the browser doesn't open, visit this URL manually:"),console.log(b.cyan(s)),console.log(),await ro(s),t.start("Waiting for authentication...");let{token:c,userId:d,email:l}=await no(n,i);await nt({apiToken:c,apiUrl:o,userId:d,email:l}),t.succeed("Authenticated successfully"),console.log(),console.log(`Logged in as: ${b.cyan(l)}`),console.log(`Config saved to: ${b.cyan(L)}`)}catch(n){t.fail("Authentication failed"),console.error(b.red(`Error: ${n instanceof Error?n.message:n}`)),console.log(),console.log("You can also authenticate manually:"),console.log(b.cyan(" tk login --token YOUR_TOKEN")),console.log(),console.log(`Get your token at: ${b.cyan(`${o}/dashboard/settings`)}`),process.exit(1)}}async function he(){let e=ue();e.start("Logging out..."),await nt({}),e.succeed("Logged out successfully"),console.log(),console.log(`Config cleared from: ${b.cyan(L)}`)}import{join as ye}from"path";import m from"chalk";import so from"ora";async function ao(e){let{readFile:t}=await import("fs/promises"),{existsSync:o}=await import("fs"),r=ye(I(e),"structure.json");try{if(o(r)){let n=await t(r,"utf-8");return JSON.parse(n)}}catch{}return null}async function co(e){let{readdir:t,stat:o}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let c of s){let d=ye(i,c);try{(await o(d)).isDirectory()?await n(d):c==="CONTEXT.md"&&r++}catch{}}}return await n(e),r}async function we(){let e=so(),t=process.cwd();try{await N(t)||(console.error(m.red("Error: Not a git repository")),process.exit(1));let o=await P(t),r=await k(),n=I(o);console.log(m.bold("ThinkerMD Status")),console.log();let i=await X(o),s=r.branch||await S(o),c=r.commitSha||await _(o);console.log(m.dim("Repository:")),console.log(` Name: ${m.cyan(i||"unknown")}`),console.log(` Branch: ${m.cyan(s)}`),console.log(` Commit: ${m.cyan(c.slice(0,7))}`),console.log();let{stat:d}=await import("fs/promises"),l=!1;try{await d(n),l=!0}catch{}if(!l){console.log(m.dim("Local Context:")),console.log(` Status: ${m.yellow("Not initialized")}`),console.log(),console.log("Run "+m.cyan("tk init")+" to create .thinker.md/");return}console.log(m.dim("Local Context:"));let a=await M(o),u=await ao(o),g=await co(n);if(console.log(` Status: ${m.green("Initialized")}`),console.log(` Project ID: ${m.cyan(a?.projectId||"unknown")}`),console.log(` Context files: ${m.cyan(g)}`),u&&(console.log(` Directories: ${m.cyan(u.directoryCount)}`),console.log(` Files tracked: ${m.cyan(u.fileCount)}`)),a?.generatedAt){let p=new Date(a.generatedAt);console.log(` Generated: ${m.cyan(p.toLocaleString())}`)}a?.pulledFrom&&console.log(` Pulled from: ${m.cyan(a.pulledFrom.slice(0,7))}`),console.log();let f=r.metaRepoId||a?.projectId;if(f&&r.r2Endpoint){console.log(m.dim("Remote Storage:")),e.start("Checking remote...");try{let p=await ct(f,s);p?(e.stop(),console.log(` Latest commit: ${m.cyan(p.slice(0,7))}`),p===c?console.log(` Status: ${m.green("Up to date")}`):a?.pulledFrom===p?console.log(` Status: ${m.yellow("Local changes not pushed")}`):(console.log(` Status: ${m.yellow("Remote has newer version")}`),console.log(),console.log("Run "+m.cyan("tk pull")+" to download latest context"))):(e.stop(),console.log(` Status: ${m.yellow("No remote context found")}`),console.log(),console.log("Run "+m.cyan("tk push")+" to upload context"))}catch{e.stop(),console.log(` Status: ${m.dim("Unable to check (no credentials)")}`)}}console.log(),console.log(m.dim("Mode:")),console.log(` ${r.mode==="sandbox"?m.cyan("Sandbox"):m.cyan("Local")}`),r.mode==="sandbox"&&console.log(` Job ID: ${m.cyan(r.jobId||"unknown")}`)}catch(o){e.fail("Failed to get status"),console.error(m.red(`Error: ${o}`)),process.exit(1)}}import w from"chalk";import lo from"ora";async function xe(e){let t=lo(),o=process.cwd();try{await N(o)||(console.error(w.red("Error: Not a git repository")),process.exit(1));let r=await P(o);if((await k()).mode==="sandbox"){console.error(w.yellow("Note: In sandbox mode, generation is triggered automatically.")),console.log("Use 'tk init' to create structure and 'tk push' when done.");return}let i=await $();i.apiToken||(console.error(w.red("Error: Not logged in")),console.log(),console.log("Please authenticate first:"),console.log(w.cyan(" tk login --token YOUR_TOKEN")),process.exit(1));let s=i.apiUrl||J,c=await X(r),d=e.branch||await S(r),l=await _(r);c||(console.error(w.red("Error: Unable to determine repository name")),console.log("Make sure this repository has a remote origin configured."),process.exit(1)),console.log(w.bold("Trigger Context Generation")),console.log(),console.log(`Repository: ${w.cyan(c)}`),console.log(`Branch: ${w.cyan(d)}`),console.log(`Commit: ${w.cyan(l.slice(0,7))}`),console.log(),t.start("Looking up meta-repository...");let a=await fetch(`${s}/api/meta-repositories/lookup?repo=${encodeURIComponent(c)}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});a.ok||(a.status===404&&(t.fail("Meta-repository not found"),console.log(),console.log("This repository is not connected to ThinkerMD. Please add it at:"),console.log(w.cyan(`${s}/dashboard`)),process.exit(1)),t.fail("API error"),console.error(w.red(`Error: ${a.status} ${a.statusText}`)),process.exit(1));let u=await a.json();t.succeed(`Found meta-repository: ${u.id}`),t.start("Triggering generation job...");let g=await fetch(`${s}/api/jobs/generate`,{method:"POST",headers:{Authorization:`Bearer ${i.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({metaRepositoryId:u.id,commitSha:l,branch:d,priority:"normal"})});if(!g.ok){t.fail("Failed to trigger generation");let p=await g.json().catch(()=>({}));console.error(w.red(`Error: ${p.error||g.statusText}`)),process.exit(1)}let f=await g.json();if(t.succeed("Generation job queued"),console.log(),console.log(`Job ID: ${w.cyan(f.jobId)}`),console.log(`Status: ${w.cyan(f.status)}`),f.estimatedPosition&&console.log(`Queue position: ${w.cyan(f.estimatedPosition)}`),console.log(),console.log("Track progress at:"),console.log(w.cyan(`${s}/dashboard/repositories/${u.id}/jobs/${f.jobId}`)),e.wait){console.log(),t.start("Waiting for completion...");let p=!1,C="";for(;!p;){await new Promise(x=>setTimeout(x,5e3));let H=await fetch(`${s}/api/jobs/${f.jobId}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});if(H.ok){let x=await H.json();x.status!==C&&(C=x.status,t.text=`Status: ${x.status} (${x.progress||0}%)`),(x.status==="completed"||x.status==="failed")&&(p=!0,x.status==="completed"?(t.succeed("Generation completed!"),console.log(),console.log("Run "+w.cyan("tk pull")+" to download the generated context")):(t.fail("Generation failed"),x.errorMessage&&console.error(w.red(`Error: ${x.errorMessage}`))))}}}}catch(r){t.fail("Failed to trigger generation"),console.error(w.red(`Error: ${r}`)),process.exit(1)}}import vt from"chalk";import{Hono as Ro}from"hono";import F from"chalk";import{createServer as To}from"http";import{homedir as Te}from"os";import{join as Pe}from"path";import{readFile as mo,writeFile as go,mkdir as fo}from"fs/promises";import{existsSync as ho}from"fs";async function po(){return(await $()).apiUrl||process.env.THINKERMD_API_URL||J}async function uo(){return(await $()).apiToken||process.env.THINKERMD_API_TOKEN||null}async function lt(e,t={}){let o=await po(),r=await uo();if(!r)throw new Error("Not authenticated. Run `tk login` first.");let{method:n="GET",body:i,params:s}=t,c=`${o}${e}`;if(s){let l=new URLSearchParams(s);c+=`?${l.toString()}`}let d=await fetch(c,{method:n,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:i?JSON.stringify(i):void 0});if(!d.ok){let l=await d.json().catch(()=>({}));throw new Error(l.error||`API request failed: ${d.status} ${d.statusText}`)}return d.json()}async function Ce(e,t,o={}){return lt("/api/mcp/search",{method:"POST",body:{repositoryId:e,query:t,branch:o.branch,limit:o.limit||10}})}async function Tt(e,t,o={}){return lt("/api/mcp/read",{params:{repositoryId:e,filePath:t,...o.branch&&{branch:o.branch}}})}async function Ee(e,t={}){return lt("/api/mcp/files",{params:{repositoryId:e,...t.branch&&{branch:t.branch}}})}async function Re(e){try{let t=await lt("/api/user/repositories",{params:{remoteUrl:e}});return t.repositories&&t.repositories.length>0?t.repositories[0]:null}catch{return null}}var Pt=Pe(Te(),".thinkermd","cache","repo-mapping.json"),yo=24*60*60*1e3;async function wo(){try{if(ho(Pt)){let e=await mo(Pt,"utf-8");return JSON.parse(e)}}catch{}return{mappings:{}}}async function xo(e){let t=Pe(Te(),".thinkermd","cache");await fo(t,{recursive:!0}),await go(Pt,JSON.stringify(e,null,2))}async function q(e){let t;try{t=await P(e)}catch{return console.error(`[RepoResolver] Not a git repository: ${e}`),null}let o=await yt(t);if(!o)return console.error(`[RepoResolver] No git remote found for: ${t}`),null;let r=Co(o),n=await wo(),i=n.mappings[r];if(i&&Date.now()-i.cachedAt<yo){console.log(`[RepoResolver] Cache hit for: ${r}`);let l=await S(t),a=await _(t);return{repositoryId:i.repositoryId,fullName:i.fullName,remoteUrl:r,gitRoot:t,branch:l||"main",commit:a||"HEAD"}}console.log(`[RepoResolver] Querying API for: ${r}`);let s=await Re(r);if(!s)return console.error(`[RepoResolver] Repository not found in ThinkerMD: ${r}`),null;n.mappings[r]={remoteUrl:r,repositoryId:s.id,fullName:s.fullName,cachedAt:Date.now()},await xo(n);let c=await S(t),d=await _(t);return{repositoryId:s.id,fullName:s.fullName,remoteUrl:r,gitRoot:t,branch:c||"main",commit:d||"HEAD"}}function Co(e){return e=e.replace(/\.git$/,""),e.startsWith("git@")&&(e=e.replace(/^git@/,"https://").replace(/:([^/])/,"/$1")),e.startsWith("http://")&&(e=e.replace("http://","https://")),e}async function dt(e){let{path:t,query:o,limit:r=10}=e,n=await q(t);if(!n)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Ce(n.repositoryId,o,{branch:n.branch,limit:r}),repositoryId:n.repositoryId,fullName:n.fullName}}async function pt(e){let{path:t,filePath:o}=e,r=await q(t);if(!r)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Tt(r.repositoryId,o,{branch:r.branch}),repositoryId:r.repositoryId,fullName:r.fullName}}async function ut(e){let{path:t}=e,o=await q(t);if(!o)throw new Error(`Could not resolve repository for path: ${t}`);let r=t;t.startsWith(o.gitRoot)&&(r=t.slice(o.gitRoot.length).replace(/^\//,""));let n=r?`.thinker.md/contexts/${r}/CONTEXT.md`:".thinker.md/contexts/CONTEXT.md";return{...await Tt(o.repositoryId,n,{branch:o.branch}),repositoryId:o.repositoryId,fullName:o.fullName}}async function mt(e){let{path:t}=e,o=await q(t);if(!o)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Ee(o.repositoryId,{branch:o.branch}),repositoryId:o.repositoryId,fullName:o.fullName}}async function ve(e){return q(e.path)}import{McpServer as Eo}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as G}from"zod";function gt(){let e=new Eo({name:"thinkermd",version:"0.1.0"});return e.tool("thinkermd_search","Semantic search across context files in the repository. Use this to find relevant documentation and context about the codebase.",{query:G.string().describe("Search query - what you're looking for"),path:G.string().optional().describe("Path to directory (defaults to current working directory)"),limit:G.number().optional().default(10).describe("Maximum number of results to return")},async({query:t,path:o,limit:r})=>{try{let n=o||process.cwd(),i=await dt({path:n,query:t,limit:r});return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Search failed"}`}],isError:!0}}}),e.tool("thinkermd_read","Read a specific context file from the repository. Use this to get the full content of a CONTEXT.md file.",{filePath:G.string().describe("Path to the context file relative to repository root"),path:G.string().optional().describe("Path to repository directory (defaults to current working directory)")},async({filePath:t,path:o})=>{try{let r=o||process.cwd();return{content:[{type:"text",text:(await pt({path:r,filePath:t})).content}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Read failed"}`}],isError:!0}}}),e.tool("thinkermd_get_context","Get the CONTEXT.md file for a specific directory. This provides contextual documentation about that part of the codebase.",{path:G.string().describe("Path to the directory to get context for")},async({path:t})=>{try{return{content:[{type:"text",text:(await ut({path:t})).content}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"Get context failed"}`}],isError:!0}}}),e.tool("thinkermd_list_files","List all CONTEXT.md files available in the repository. Use this to discover what documentation is available.",{path:G.string().optional().describe("Path to repository directory (defaults to current working directory)")},async({path:t})=>{try{let o=t||process.cwd(),r=await mt({path:o});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"List files failed"}`}],isError:!0}}}),e}import{StreamableHTTPServerTransport as Po}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{InitializeRequestSchema as vo}from"@modelcontextprotocol/sdk/types.js";var j=new Map;function bo(e){let t=o=>{let r=vo.safeParse(o);return console.log("[MCP DEBUG] safeParse result:",r.success,r.success?"":r.error),r.success};return Array.isArray(e)?(console.log("[MCP DEBUG] Body is array, checking each request"),e.some(o=>t(o))):(console.log("[MCP DEBUG] Body is single request"),t(e))}async function ko(e){let t=[];for await(let o of e)t.push(o);return Buffer.concat(t).toString()}async function So(e,t){let o=e.headers["mcp-session-id"],r=await ko(e),n;try{n=JSON.parse(r)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error: Invalid JSON"},id:null}));return}if(o&&j.has(o)){await j.get(o).handleRequest(e,t,r);return}if(console.log("[MCP DEBUG] Received body:",JSON.stringify(n,null,2)),console.log("[MCP DEBUG] Session ID:",o),console.log("[MCP DEBUG] Request headers:",JSON.stringify(e.headers,null,2)),console.log("[MCP DEBUG] Request method:",e.method),console.log("[MCP DEBUG] Content-Type:",e.headers["content-type"]),!o&&bo(n)){console.log("[MCP] New session initializing...");let i=new Po({sessionIdGenerator:()=>crypto.randomUUID(),enableJsonResponse:!0,onsessioninitialized:a=>{j.set(a,i),console.log(`[MCP] Session started: ${a}`)}});i.onclose=()=>{let a=i.sessionId;a&&j.has(a)&&(console.log(`[MCP] Session closed: ${a}`),j.delete(a))},i.onerror=a=>{console.error("[MCP DEBUG] Transport error:",a)};let s=gt();console.log("[MCP DEBUG] Connecting server to transport..."),await s.connect(i),console.log("[MCP DEBUG] Server connected, handling request...");let c=t.write.bind(t),d=t.end.bind(t),l="";t.write=function(a,...u){return a&&(l+=a.toString()),c(a,...u)},t.end=function(a,...u){return a&&(l+=a.toString()),console.log("[MCP DEBUG] Response body:",l),d(a,...u)};try{await i.handleRequest(e,t,r),console.log("[MCP DEBUG] handleRequest completed"),console.log("[MCP DEBUG] Transport sessionId after handle:",i.sessionId),console.log("[MCP DEBUG] Response headers sent:",t.headersSent),console.log("[MCP DEBUG] Response statusCode:",t.statusCode)}catch(a){throw console.error("[MCP DEBUG] handleRequest error:",a),a}return}t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null}))}async function $o(e,t){let o=e.headers["mcp-session-id"];if(!o||!j.has(o)){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid or missing session ID"}));return}console.log(`[MCP] SSE stream requested for session: ${o}`),await j.get(o).handleRequest(e,t)}async function Io(e,t){let o=e.headers["mcp-session-id"];if(!o||!j.has(o)){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid or missing session ID"}));return}console.log(`[MCP] Session termination requested: ${o}`),await j.get(o).handleRequest(e,t)}function No(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id, Authorization"),e.setHeader("Access-Control-Expose-Headers","mcp-session-id")}async function _o(e,t){if(No(t),e.method==="OPTIONS"){t.writeHead(204),t.end();return}try{e.method==="POST"?await So(e,t):e.method==="GET"?await $o(e,t):e.method==="DELETE"?await Io(e,t):(t.writeHead(405,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Method not allowed"})))}catch(o){console.error("[MCP] Error handling request:",o),t.headersSent||(t.writeHead(500,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal server error"},id:null})))}}function Ao(){let e=new Ro;return e.get("/health",t=>t.json({status:"ok",version:"0.1.0"})),e.post("/mcp/search",async t=>{try{let o=await t.req.json(),{path:r,query:n,limit:i}=o;if(!r||!n)return t.json({error:"path and query are required"},400);let s=await dt({path:r,query:n,limit:i});return t.json(s)}catch(o){return console.error("[MCP] Search error:",o),t.json({error:o instanceof Error?o.message:"Search failed"},500)}}),e.get("/mcp/read",async t=>{try{let o=t.req.query("path"),r=t.req.query("filePath");if(!o||!r)return t.json({error:"path and filePath are required"},400);let n=await pt({path:o,filePath:r});return t.json(n)}catch(o){return console.error("[MCP] Read error:",o),t.json({error:o instanceof Error?o.message:"Read failed"},500)}}),e.get("/mcp/context",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await ut({path:o});return t.json(r)}catch(o){return console.error("[MCP] Get context error:",o),t.json({error:o instanceof Error?o.message:"Get context failed"},500)}}),e.get("/mcp/files",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await mt({path:o});return t.json(r)}catch(o){return console.error("[MCP] List files error:",o),t.json({error:o instanceof Error?o.message:"List files failed"},500)}}),e.get("/mcp/repo",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await ve({path:o});return r?t.json({connected:!0,...r}):t.json({connected:!1,message:"Repository not found in ThinkerMD"})}catch(o){return console.error("[MCP] Get repo info error:",o),t.json({error:o instanceof Error?o.message:"Get repo info failed"},500)}}),e}function Oo(e,t){let o=`http://localhost:${e}/mcp`,r=64,n=40,i=t.length>n?t.slice(0,n-3)+"...":t;console.log(F.cyan(`
103
+ </html>`),c(),r({token:u,userId:g,email:f})):(l.writeHead(400,{"Content-Type":"text/plain"}),l.end("Missing required parameters"))}else l.writeHead(404,{"Content-Type":"text/plain"}),l.end("Not found")}),i.listen(e)})}async function io(e,t){try{let o=await fetch(`${e}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return o.ok?{valid:!0,userId:(await o.json()).userId}:o.status===401?{valid:!1,error:"Invalid or expired token"}:{valid:!1,error:`API error: ${o.status} ${o.statusText}`}}catch(o){return{valid:!1,error:`Connection failed: ${o}`}}}async function fe(e){let t=ue(),o=e.apiUrl||J;if(e.token){t.start("Validating token...");let n=await io(o,e.token);n.valid||(t.fail("Authentication failed"),console.error(b.red(`Error: ${n.error}`)),process.exit(1));let i={apiToken:e.token,apiUrl:o,userId:n.userId};await nt(i),t.succeed("Authenticated successfully"),console.log(),console.log(`Config saved to: ${b.cyan(L)}`);return}let r=await $();if(r.apiToken){console.log(b.green("You are already logged in.")),console.log(`User ID: ${b.cyan(r.userId||"unknown")}`),console.log(),console.log("To re-authenticate, run:"),console.log(b.cyan(" tk login --token NEW_TOKEN")),console.log(),console.log("Or to login with a different account:"),console.log(b.cyan(" tk logout && tk login"));return}console.log(b.bold("ThinkerMD Login")),console.log(),t.start("Setting up authentication...");try{let n=await ge(),i=to(16).toString("hex"),s=`${o}/cli/auth?port=${n}&state=${i}`;t.succeed("Ready for authentication"),console.log(),console.log("Opening browser for authentication..."),console.log(),console.log("If the browser doesn't open, visit this URL manually:"),console.log(b.cyan(s)),console.log(),await ro(s),t.start("Waiting for authentication...");let{token:c,userId:d,email:l}=await no(n,i);await nt({apiToken:c,apiUrl:o,userId:d,email:l}),t.succeed("Authenticated successfully"),console.log(),console.log(`Logged in as: ${b.cyan(l)}`),console.log(`Config saved to: ${b.cyan(L)}`)}catch(n){t.fail("Authentication failed"),console.error(b.red(`Error: ${n instanceof Error?n.message:n}`)),console.log(),console.log("You can also authenticate manually:"),console.log(b.cyan(" tk login --token YOUR_TOKEN")),console.log(),console.log(`Get your token at: ${b.cyan(`${o}/dashboard/settings`)}`),process.exit(1)}}async function he(){let e=ue();e.start("Logging out..."),await nt({}),e.succeed("Logged out successfully"),console.log(),console.log(`Config cleared from: ${b.cyan(L)}`)}import{join as ye}from"path";import m from"chalk";import so from"ora";async function ao(e){let{readFile:t}=await import("fs/promises"),{existsSync:o}=await import("fs"),r=ye(I(e),"structure.json");try{if(o(r)){let n=await t(r,"utf-8");return JSON.parse(n)}}catch{}return null}async function co(e){let{readdir:t,stat:o}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let c of s){let d=ye(i,c);try{(await o(d)).isDirectory()?await n(d):c==="CONTEXT.md"&&r++}catch{}}}return await n(e),r}async function we(){let e=so(),t=process.cwd();try{await N(t)||(console.error(m.red("Error: Not a git repository")),process.exit(1));let o=await P(t),r=await k(),n=I(o);console.log(m.bold("ThinkerMD Status")),console.log();let i=await X(o),s=r.branch||await S(o),c=r.commitSha||await _(o);console.log(m.dim("Repository:")),console.log(` Name: ${m.cyan(i||"unknown")}`),console.log(` Branch: ${m.cyan(s)}`),console.log(` Commit: ${m.cyan(c.slice(0,7))}`),console.log();let{stat:d}=await import("fs/promises"),l=!1;try{await d(n),l=!0}catch{}if(!l){console.log(m.dim("Local Context:")),console.log(` Status: ${m.yellow("Not initialized")}`),console.log(),console.log("Run "+m.cyan("tk init")+" to create .thinker.md/");return}console.log(m.dim("Local Context:"));let a=await M(o),u=await ao(o),g=await co(n);if(console.log(` Status: ${m.green("Initialized")}`),console.log(` Project ID: ${m.cyan(a?.projectId||"unknown")}`),console.log(` Context files: ${m.cyan(g)}`),u&&(console.log(` Directories: ${m.cyan(u.directoryCount)}`),console.log(` Files tracked: ${m.cyan(u.fileCount)}`)),a?.generatedAt){let p=new Date(a.generatedAt);console.log(` Generated: ${m.cyan(p.toLocaleString())}`)}a?.pulledFrom&&console.log(` Pulled from: ${m.cyan(a.pulledFrom.slice(0,7))}`),console.log();let f=r.metaRepoId||a?.projectId;if(f&&r.r2Endpoint){console.log(m.dim("Remote Storage:")),e.start("Checking remote...");try{let p=await ct(f,s);p?(e.stop(),console.log(` Latest commit: ${m.cyan(p.slice(0,7))}`),p===c?console.log(` Status: ${m.green("Up to date")}`):a?.pulledFrom===p?console.log(` Status: ${m.yellow("Local changes not pushed")}`):(console.log(` Status: ${m.yellow("Remote has newer version")}`),console.log(),console.log("Run "+m.cyan("tk pull")+" to download latest context"))):(e.stop(),console.log(` Status: ${m.yellow("No remote context found")}`),console.log(),console.log("Run "+m.cyan("tk push")+" to upload context"))}catch{e.stop(),console.log(` Status: ${m.dim("Unable to check (no credentials)")}`)}}console.log(),console.log(m.dim("Mode:")),console.log(` ${r.mode==="sandbox"?m.cyan("Sandbox"):m.cyan("Local")}`),r.mode==="sandbox"&&console.log(` Job ID: ${m.cyan(r.jobId||"unknown")}`)}catch(o){e.fail("Failed to get status"),console.error(m.red(`Error: ${o}`)),process.exit(1)}}import w from"chalk";import lo from"ora";async function xe(e){let t=lo(),o=process.cwd();try{await N(o)||(console.error(w.red("Error: Not a git repository")),process.exit(1));let r=await P(o);if((await k()).mode==="sandbox"){console.error(w.yellow("Note: In sandbox mode, generation is triggered automatically.")),console.log("Use 'tk init' to create structure and 'tk push' when done.");return}let i=await $();i.apiToken||(console.error(w.red("Error: Not logged in")),console.log(),console.log("Please authenticate first:"),console.log(w.cyan(" tk login --token YOUR_TOKEN")),process.exit(1));let s=i.apiUrl||J,c=await X(r),d=e.branch||await S(r),l=await _(r);c||(console.error(w.red("Error: Unable to determine repository name")),console.log("Make sure this repository has a remote origin configured."),process.exit(1)),console.log(w.bold("Trigger Context Generation")),console.log(),console.log(`Repository: ${w.cyan(c)}`),console.log(`Branch: ${w.cyan(d)}`),console.log(`Commit: ${w.cyan(l.slice(0,7))}`),console.log(),t.start("Looking up meta-repository...");let a=await fetch(`${s}/api/meta-repositories/lookup?repo=${encodeURIComponent(c)}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});a.ok||(a.status===404&&(t.fail("Meta-repository not found"),console.log(),console.log("This repository is not connected to ThinkerMD. Please add it at:"),console.log(w.cyan(`${s}/dashboard`)),process.exit(1)),t.fail("API error"),console.error(w.red(`Error: ${a.status} ${a.statusText}`)),process.exit(1));let u=await a.json();t.succeed(`Found meta-repository: ${u.id}`),t.start("Triggering generation job...");let g=await fetch(`${s}/api/jobs/generate`,{method:"POST",headers:{Authorization:`Bearer ${i.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({metaRepositoryId:u.id,commitSha:l,branch:d,priority:"normal"})});if(!g.ok){t.fail("Failed to trigger generation");let p=await g.json().catch(()=>({}));console.error(w.red(`Error: ${p.error||g.statusText}`)),process.exit(1)}let f=await g.json();if(t.succeed("Generation job queued"),console.log(),console.log(`Job ID: ${w.cyan(f.jobId)}`),console.log(`Status: ${w.cyan(f.status)}`),f.estimatedPosition&&console.log(`Queue position: ${w.cyan(f.estimatedPosition)}`),console.log(),console.log("Track progress at:"),console.log(w.cyan(`${s}/dashboard/repositories/${u.id}/jobs/${f.jobId}`)),e.wait){console.log(),t.start("Waiting for completion...");let p=!1,C="";for(;!p;){await new Promise(x=>setTimeout(x,5e3));let H=await fetch(`${s}/api/jobs/${f.jobId}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});if(H.ok){let x=await H.json();x.status!==C&&(C=x.status,t.text=`Status: ${x.status} (${x.progress||0}%)`),(x.status==="completed"||x.status==="failed")&&(p=!0,x.status==="completed"?(t.succeed("Generation completed!"),console.log(),console.log("Run "+w.cyan("tk pull")+" to download the generated context")):(t.fail("Generation failed"),x.errorMessage&&console.error(w.red(`Error: ${x.errorMessage}`))))}}}}catch(r){t.fail("Failed to trigger generation"),console.error(w.red(`Error: ${r}`)),process.exit(1)}}import vt from"chalk";import{Hono as Ro}from"hono";import F from"chalk";import{createServer as To}from"http";import{homedir as Te}from"os";import{join as Pe}from"path";import{readFile as mo,writeFile as go,mkdir as fo}from"fs/promises";import{existsSync as ho}from"fs";async function po(){return(await $()).apiUrl||process.env.THINKERMD_API_URL||J}async function uo(){return(await $()).apiToken||process.env.THINKERMD_API_TOKEN||null}async function lt(e,t={}){let o=await po(),r=await uo();if(!r)throw new Error("Not authenticated. Run `tk login` first.");let{method:n="GET",body:i,params:s}=t,c=`${o}${e}`;if(s){let l=new URLSearchParams(s);c+=`?${l.toString()}`}let d=await fetch(c,{method:n,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:i?JSON.stringify(i):void 0});if(!d.ok){let l=await d.json().catch(()=>({}));throw new Error(l.error||`API request failed: ${d.status} ${d.statusText}`)}return d.json()}async function Ce(e,t,o={}){return lt("/api/mcp/search",{method:"POST",body:{repositoryId:e,query:t,branch:o.branch,limit:o.limit||10}})}async function Tt(e,t,o={}){return lt("/api/mcp/read",{params:{repositoryId:e,filePath:t,...o.branch&&{branch:o.branch}}})}async function Ee(e,t={}){return lt("/api/mcp/files",{params:{repositoryId:e,...t.branch&&{branch:t.branch}}})}async function Re(e){try{let t=await lt("/api/user/repositories",{params:{remoteUrl:e}});return t.repositories&&t.repositories.length>0?t.repositories[0]:null}catch{return null}}var Pt=Pe(Te(),".thinkermd","cache","repo-mapping.json"),yo=24*60*60*1e3;async function wo(){try{if(ho(Pt)){let e=await mo(Pt,"utf-8");return JSON.parse(e)}}catch{}return{mappings:{}}}async function xo(e){let t=Pe(Te(),".thinkermd","cache");await fo(t,{recursive:!0}),await go(Pt,JSON.stringify(e,null,2))}async function q(e){let t;try{t=await P(e)}catch{return console.error(`[RepoResolver] Not a git repository: ${e}`),null}let o=await yt(t);if(!o)return console.error(`[RepoResolver] No git remote found for: ${t}`),null;let r=Co(o),n=await wo(),i=n.mappings[r];if(i&&Date.now()-i.cachedAt<yo){console.log(`[RepoResolver] Cache hit for: ${r}`);let l=await S(t),a=await _(t);return{repositoryId:i.repositoryId,fullName:i.fullName,remoteUrl:r,gitRoot:t,branch:l||"main",commit:a||"HEAD"}}console.log(`[RepoResolver] Querying API for: ${r}`);let s=await Re(r);if(!s)return console.error(`[RepoResolver] Repository not found in ThinkerMD: ${r}`),null;n.mappings[r]={remoteUrl:r,repositoryId:s.id,fullName:s.fullName,cachedAt:Date.now()},await xo(n);let c=await S(t),d=await _(t);return{repositoryId:s.id,fullName:s.fullName,remoteUrl:r,gitRoot:t,branch:c||"main",commit:d||"HEAD"}}function Co(e){return e=e.replace(/\.git$/,""),e.startsWith("git@")&&(e=e.replace(/^git@/,"https://").replace(/:([^/])/,"/$1")),e.startsWith("http://")&&(e=e.replace("http://","https://")),e}async function dt(e){let{path:t,query:o,limit:r=10}=e,n=await q(t);if(!n)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Ce(n.repositoryId,o,{branch:n.branch,limit:r}),repositoryId:n.repositoryId,fullName:n.fullName}}async function pt(e){let{path:t,filePath:o}=e,r=await q(t);if(!r)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Tt(r.repositoryId,o,{branch:r.branch}),repositoryId:r.repositoryId,fullName:r.fullName}}async function ut(e){let{path:t}=e,o=await q(t);if(!o)throw new Error(`Could not resolve repository for path: ${t}`);let r=t;t.startsWith(o.gitRoot)&&(r=t.slice(o.gitRoot.length).replace(/^\//,""));let n=r?`.thinker.md/contexts/${r}/CONTEXT.md`:".thinker.md/contexts/CONTEXT.md";return{...await Tt(o.repositoryId,n,{branch:o.branch}),repositoryId:o.repositoryId,fullName:o.fullName}}async function mt(e){let{path:t}=e,o=await q(t);if(!o)throw new Error(`Could not resolve repository for path: ${t}`);return{...await Ee(o.repositoryId,{branch:o.branch}),repositoryId:o.repositoryId,fullName:o.fullName}}async function ve(e){return q(e.path)}import{McpServer as Eo}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as G}from"zod";function gt(){let e=new Eo({name:"thinkermd",version:"0.1.0"});return e.tool("thinkermd_search","Semantic search across context files in the repository. Use this to find relevant documentation and context about the codebase.",{query:G.string().describe("Search query - what you're looking for"),path:G.string().optional().describe("Path to directory (defaults to current working directory)"),limit:G.number().optional().default(10).describe("Maximum number of results to return")},async({query:t,path:o,limit:r})=>{try{let n=o||process.cwd(),i=await dt({path:n,query:t,limit:r});return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Search failed"}`}],isError:!0}}}),e.tool("thinkermd_read","Read a specific context file from the repository. Use this to get the full content of a CONTEXT.md file.",{filePath:G.string().describe("Path to the context file relative to repository root"),path:G.string().optional().describe("Path to repository directory (defaults to current working directory)")},async({filePath:t,path:o})=>{try{let r=o||process.cwd();return{content:[{type:"text",text:(await pt({path:r,filePath:t})).content}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Read failed"}`}],isError:!0}}}),e.tool("thinkermd_get_context","Get the CONTEXT.md file for a specific directory. This provides contextual documentation about that part of the codebase.",{path:G.string().describe("Path to the directory to get context for")},async({path:t})=>{try{return{content:[{type:"text",text:(await ut({path:t})).content}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"Get context failed"}`}],isError:!0}}}),e.tool("thinkermd_list_files","List all CONTEXT.md files available in the repository. Use this to discover what documentation is available.",{path:G.string().optional().describe("Path to repository directory (defaults to current working directory)")},async({path:t})=>{try{let o=t||process.cwd(),r=await mt({path:o});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"List files failed"}`}],isError:!0}}}),e}import{StreamableHTTPServerTransport as Po}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{InitializeRequestSchema as vo}from"@modelcontextprotocol/sdk/types.js";var j=new Map;function bo(e){let t=o=>{let r=vo.safeParse(o);return console.log("[MCP DEBUG] safeParse result:",r.success,r.success?"":r.error),r.success};return Array.isArray(e)?(console.log("[MCP DEBUG] Body is array, checking each request"),e.some(o=>t(o))):(console.log("[MCP DEBUG] Body is single request"),t(e))}async function ko(e){let t=[];for await(let o of e)t.push(o);return Buffer.concat(t).toString()}async function So(e,t){let o=e.headers["mcp-session-id"],r=await ko(e),n;try{n=JSON.parse(r)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error: Invalid JSON"},id:null}));return}if(o&&j.has(o)){await j.get(o).handleRequest(e,t,n);return}if(console.log("[MCP DEBUG] Received body:",JSON.stringify(n,null,2)),console.log("[MCP DEBUG] Session ID:",o),console.log("[MCP DEBUG] Request headers:",JSON.stringify(e.headers,null,2)),console.log("[MCP DEBUG] Request method:",e.method),console.log("[MCP DEBUG] Content-Type:",e.headers["content-type"]),!o&&bo(n)){console.log("[MCP] New session initializing...");let i=new Po({sessionIdGenerator:()=>crypto.randomUUID(),enableJsonResponse:!0,onsessioninitialized:a=>{j.set(a,i),console.log(`[MCP] Session started: ${a}`)}});i.onclose=()=>{let a=i.sessionId;a&&j.has(a)&&(console.log(`[MCP] Session closed: ${a}`),j.delete(a))},i.onerror=a=>{console.error("[MCP DEBUG] Transport error:",a)};let s=gt();console.log("[MCP DEBUG] Connecting server to transport..."),await s.connect(i),console.log("[MCP DEBUG] Server connected, handling request...");let c=t.write.bind(t),d=t.end.bind(t),l="";t.write=function(a,...u){return a&&(l+=a.toString()),c(a,...u)},t.end=function(a,...u){return a&&(l+=a.toString()),console.log("[MCP DEBUG] Response body:",l),d(a,...u)};try{await i.handleRequest(e,t,n),console.log("[MCP DEBUG] handleRequest completed"),console.log("[MCP DEBUG] Transport sessionId after handle:",i.sessionId),console.log("[MCP DEBUG] Response headers sent:",t.headersSent),console.log("[MCP DEBUG] Response statusCode:",t.statusCode)}catch(a){throw console.error("[MCP DEBUG] handleRequest error:",a),a}return}t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null}))}async function $o(e,t){let o=e.headers["mcp-session-id"];if(!o||!j.has(o)){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid or missing session ID"}));return}console.log(`[MCP] SSE stream requested for session: ${o}`),await j.get(o).handleRequest(e,t)}async function Io(e,t){let o=e.headers["mcp-session-id"];if(!o||!j.has(o)){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid or missing session ID"}));return}console.log(`[MCP] Session termination requested: ${o}`),await j.get(o).handleRequest(e,t)}function No(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id, Authorization"),e.setHeader("Access-Control-Expose-Headers","mcp-session-id")}async function _o(e,t){if(No(t),e.method==="OPTIONS"){t.writeHead(204),t.end();return}try{e.method==="POST"?await So(e,t):e.method==="GET"?await $o(e,t):e.method==="DELETE"?await Io(e,t):(t.writeHead(405,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Method not allowed"})))}catch(o){console.error("[MCP] Error handling request:",o),t.headersSent||(t.writeHead(500,{"Content-Type":"application/json"}),t.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal server error"},id:null})))}}function Ao(){let e=new Ro;return e.get("/health",t=>t.json({status:"ok",version:"0.1.0"})),e.post("/mcp/search",async t=>{try{let o=await t.req.json(),{path:r,query:n,limit:i}=o;if(!r||!n)return t.json({error:"path and query are required"},400);let s=await dt({path:r,query:n,limit:i});return t.json(s)}catch(o){return console.error("[MCP] Search error:",o),t.json({error:o instanceof Error?o.message:"Search failed"},500)}}),e.get("/mcp/read",async t=>{try{let o=t.req.query("path"),r=t.req.query("filePath");if(!o||!r)return t.json({error:"path and filePath are required"},400);let n=await pt({path:o,filePath:r});return t.json(n)}catch(o){return console.error("[MCP] Read error:",o),t.json({error:o instanceof Error?o.message:"Read failed"},500)}}),e.get("/mcp/context",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await ut({path:o});return t.json(r)}catch(o){return console.error("[MCP] Get context error:",o),t.json({error:o instanceof Error?o.message:"Get context failed"},500)}}),e.get("/mcp/files",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await mt({path:o});return t.json(r)}catch(o){return console.error("[MCP] List files error:",o),t.json({error:o instanceof Error?o.message:"List files failed"},500)}}),e.get("/mcp/repo",async t=>{try{let o=t.req.query("path");if(!o)return t.json({error:"path is required"},400);let r=await ve({path:o});return r?t.json({connected:!0,...r}):t.json({connected:!1,message:"Repository not found in ThinkerMD"})}catch(o){return console.error("[MCP] Get repo info error:",o),t.json({error:o instanceof Error?o.message:"Get repo info failed"},500)}}),e}function Oo(e,t){let o=`http://localhost:${e}/mcp`,r=64,n=40,i=t.length>n?t.slice(0,n-3)+"...":t;console.log(F.cyan(`
104
104
  \u2554${"\u2550".repeat(r)}\u2557
105
105
  \u2551${" ".repeat(r)}\u2551
106
106
  \u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkermd/cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "ThinkerMD CLI for managing .thinker.md/ context files",
5
5
  "type": "module",
6
6
  "bin": {