@thinkermd/cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/tk.js +3 -3
  2. package/package.json +1 -1
package/dist/tk.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{Command as Fo}from"commander";import eo from"chalk";import{join as et}from"path";import{writeFile as Dt}from"fs/promises";import A from"chalk";import yo from"ora";import{homedir as Rt}from"os";import{join as U}from"path";import{readFile as $t,writeFile as Pt,mkdir as no}from"fs/promises";import{existsSync as St}from"fs";var m=".thinker.md",b="config.json",q="structure.json",v="CONTEXT.md",gt="contexts",pt="thinker.md.tar.gz",mt="metadata.json",ft="latest",W="repos",Q="branches";var ht="thinkermd-meta-repos",yt="1.0",nt="full",wt="incremental",Ct=["node_modules",".git","dist","build",".next",".nuxt","coverage","__pycache__",".pytest_cache","venv",".venv","vendor","target",m],xt=3,Et=1e3,Tt="[Describe the purpose",kt=100;var M=U(Rt(),".thinkermd","config.json"),Z="https://app.thinkermd.dev";function it(){return process.env.META_REPO_ID&&process.env.JOB_TOKEN?"sandbox":"local"}function io(){return{mode:"sandbox",metaRepoId:process.env.META_REPO_ID,branch:process.env.BRANCH,commitSha:process.env.COMMIT_SHA,previousCommitSha:process.env.PREVIOUS_COMMIT_SHA||void 0,jobId:process.env.JOB_ID,callbackUrl:process.env.CALLBACK_URL,jobToken:process.env.JOB_TOKEN,r2Endpoint:process.env.R2_ENDPOINT,r2AccessKeyId:process.env.R2_ACCESS_KEY_ID,r2SecretAccessKey:process.env.R2_SECRET_ACCESS_KEY,r2Bucket:process.env.R2_BUCKET}}async function J(){try{if(St(M)){let o=await $t(M,"utf-8");return JSON.parse(o)}}catch{}return{}}async function st(o){let t=U(Rt(),".thinkermd");await no(t,{recursive:!0}),await Pt(M,JSON.stringify(o,null,2))}async function k(){return it()==="sandbox"?io():{mode:"local",...await J()}}var G={version:yt,ignore:[...Ct]};async function _(o){let t=U(o,m,b);try{if(St(t)){let e=await $t(t,"utf-8");return JSON.parse(e)}}catch{}return null}async function tt(o,t){let e=U(o,m,b);await Pt(e,JSON.stringify(t,null,2))}function bt(){let t=["META_REPO_ID","BRANCH","COMMIT_SHA","JOB_ID","CALLBACK_URL","JOB_TOKEN","R2_ENDPOINT","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY","R2_BUCKET"].filter(e=>!process.env[e]);if(t.length>0)throw new Error(`Missing required environment variables: ${t.join(", ")}`)}function $(o){return U(o,m)}function V(o){return U(o,m,gt)}import{exec as so}from"child_process";import{promisify as ao}from"util";var co=ao(so);async function B(o,t="."){let{stdout:e}=await co(`git ${o}`,{cwd:t});return e.trim()}async function I(o="."){try{return await B("branch --show-current",o)}catch(t){throw new Error(`Failed to get current branch: ${t}`)}}async function F(o="."){try{return await B("rev-parse HEAD",o)}catch(t){throw new Error(`Failed to get current commit: ${t}`)}}async function P(o="."){try{return await B("rev-parse --show-toplevel",o)}catch(t){throw new Error(`Not a git repository: ${t}`)}}async function S(o="."){try{return await B("rev-parse --git-dir",o),!0}catch{return!1}}async function vt(o=".",t){try{return await B(`log -1 --format=%s ${t||"HEAD"}`,o)}catch(e){throw new Error(`Failed to get commit message: ${e}`)}}async function lo(o="."){try{return await B("remote get-url origin",o)}catch{return null}}async function K(o="."){let t=await lo(o);if(!t)return null;let e=t.match(/[/:]([\w-]+)\/([\w.-]+?)(\.git)?$/);return e?`${e[1]}/${e[2]}`:null}import{join as j,relative as uo,dirname as go}from"path";import{readFile as po}from"fs/promises";import{existsSync as _t}from"fs";async function mo(){let o=await import("ignore");return(o.default??o)()}var fo=[".git",".thinker.md","node_modules","__pycache__",".pytest_cache","venv",".venv",".next",".nuxt","dist","build","coverage","target",".idea",".vscode","*.log","*.lock","package-lock.json","yarn.lock","bun.lockb","pnpm-lock.yaml","Cargo.lock","poetry.lock","Gemfile.lock","*.pyc","*.pyo","*.class","*.o","*.so","*.dylib","*.dll","*.exe"];async function ho(o){let t=await mo();t.add(fo),t.add(G.ignore||[]);try{let e=j(o,".gitignore");if(_t(e)){let r=await po(e,"utf-8");t.add(r)}}catch{}return t}async function ot(o){let t=await ho(o),e=new Set,r={total:0};await It(o,o,t,e,r);let n=Array.from(e).sort();return{version:"1.0",generatedAt:new Date().toISOString(),directories:n,directoryCount:n.length,fileCount:r.total}}async function It(o,t,e,r,n){let{readdir:i,stat:s}=await import("fs/promises"),a;try{a=await i(t)}catch{return}for(let l of a){let g=j(t,l),d=uo(o,g);if(!(e.ignores(d)||e.ignores(d+"/")))try{let f=await s(g);f.isDirectory()?(r.add(d),await It(o,g,e,r,n)):f.isFile()&&n.total++}catch{}}}async function At(o){let t=j(o,".thinker.md","contexts"),e=[];return _t(j(o,".thinker.md","CONTEXT.md"))&&e.push(""),await Nt(t,"",e),e}async function Nt(o,t,e){let{readdir:r,stat:n}=await import("fs/promises"),i=t?j(o,t):o,s;try{s=await r(i)}catch{return}for(let a of s){let l=j(i,a),g=t?j(t,a):a;try{(await n(l)).isDirectory()?await Nt(o,g,e):a==="CONTEXT.md"&&e.push(go(g))}catch{}}}async function p(o,t,e){let r=await k();if(r.mode!=="sandbox"||!r.callbackUrl||!r.jobToken)return;let n=r.callbackUrl.replace("/callback","/progress"),i={type:"progress",status:"processing",step:o,progress:t,message:e};try{let s=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${r.jobToken}`,"Content-Type":"application/json"},body:JSON.stringify(i)});s.ok||console.warn(`[Progress] Update failed: ${s.status} ${s.statusText}`)}catch(s){console.warn("[Progress] Update error:",s)}}async function Ot(o,t){let e=await k();if(e.mode!=="sandbox"||!e.callbackUrl||!e.jobToken)return;let r={jobId:e.jobId,status:"completed",artifactUrl:o,stats:t};try{let n=await fetch(e.callbackUrl,{method:"POST",headers:{Authorization:`Bearer ${e.jobToken}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok)throw console.error(`[Callback] Completion failed: ${n.status} ${n.statusText}`),new Error(`Callback failed: ${n.status}`);console.log("[Callback] Job completed successfully")}catch(n){throw console.error("[Callback] Completion error:",n),n}}async function Ft(o,t){let r=`# ${t||"Project Root"}
2
+ import{Command as Fo}from"commander";import eo from"chalk";import{join as et}from"path";import{writeFile as Dt}from"fs/promises";import k from"chalk";import yo from"ora";import{homedir as $t}from"os";import{join as U}from"path";import{readFile as Rt,writeFile as Pt,mkdir as no}from"fs/promises";import{existsSync as St}from"fs";var f=".thinker.md",v="config.json",q="structure.json",_="CONTEXT.md",ut="contexts",mt="thinker.md.tar.gz",pt="metadata.json",ft="latest",W="repos",Q="branches";var ht="thinkermd-meta-repos",yt="1.0",nt="full",wt="incremental",Ct=["node_modules",".git","dist","build",".next",".nuxt","coverage","__pycache__",".pytest_cache","venv",".venv","vendor","target",f],xt=3,Et=1e3,Tt="[Describe the purpose",kt=100;var M=U($t(),".thinkermd","config.json"),Z="https://app.thinkermd.dev";function it(){return process.env.META_REPO_ID&&process.env.JOB_TOKEN?"sandbox":"local"}function io(){return{mode:"sandbox",metaRepoId:process.env.META_REPO_ID,branch:process.env.BRANCH,commitSha:process.env.COMMIT_SHA,previousCommitSha:process.env.PREVIOUS_COMMIT_SHA||void 0,jobId:process.env.JOB_ID,callbackUrl:process.env.CALLBACK_URL,jobToken:process.env.JOB_TOKEN,r2Endpoint:process.env.R2_ENDPOINT,r2AccessKeyId:process.env.R2_ACCESS_KEY_ID,r2SecretAccessKey:process.env.R2_SECRET_ACCESS_KEY,r2Bucket:process.env.R2_BUCKET}}async function J(){try{if(St(M)){let o=await Rt(M,"utf-8");return JSON.parse(o)}}catch{}return{}}async function st(o){let t=U($t(),".thinkermd");await no(t,{recursive:!0,mode:448}),await Pt(M,JSON.stringify(o,null,2),{mode:384})}async function $(){return it()==="sandbox"?io():{mode:"local",...await J()}}var G={version:yt,ignore:[...Ct]};async function I(o){let t=U(o,f,v);try{if(St(t)){let e=await Rt(t,"utf-8");return JSON.parse(e)}}catch{}return null}async function tt(o,t){let e=U(o,f,v);await Pt(e,JSON.stringify(t,null,2))}function bt(){let t=["META_REPO_ID","BRANCH","COMMIT_SHA","JOB_ID","CALLBACK_URL","JOB_TOKEN","R2_ENDPOINT","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY","R2_BUCKET"].filter(e=>!process.env[e]);if(t.length>0)throw new Error(`Missing required environment variables: ${t.join(", ")}`)}function P(o){return U(o,f)}function V(o){return U(o,f,ut)}import{execFile as so}from"child_process";import{promisify as ao}from"util";var co=ao(so);async function B(o,t="."){let{stdout:e}=await co("git",o,{cwd:t});return e.trim()}async function A(o="."){try{return await B(["branch","--show-current"],o)}catch(t){throw new Error(`Failed to get current branch: ${t}`)}}async function F(o="."){try{return await B(["rev-parse","HEAD"],o)}catch(t){throw new Error(`Failed to get current commit: ${t}`)}}async function S(o="."){try{return await B(["rev-parse","--show-toplevel"],o)}catch(t){throw new Error(`Not a git repository: ${t}`)}}async function b(o="."){try{return await B(["rev-parse","--git-dir"],o),!0}catch{return!1}}async function vt(o=".",t){try{return await B(["log","-1","--format=%s",t||"HEAD"],o)}catch(e){throw new Error(`Failed to get commit message: ${e}`)}}async function lo(o="."){try{return await B(["remote","get-url","origin"],o)}catch{return null}}async function K(o="."){let t=await lo(o);if(!t)return null;let e=t.match(/[/:]([\w-]+)\/([\w.-]+?)(\.git)?$/);return e?`${e[1]}/${e[2]}`:null}import{join as j,relative as go,dirname as uo}from"path";import{readFile as mo}from"fs/promises";import{existsSync as _t}from"fs";async function po(){let o=await import("ignore");return(o.default??o)()}var fo=[".git",".thinker.md","node_modules","__pycache__",".pytest_cache","venv",".venv",".next",".nuxt","dist","build","coverage","target",".idea",".vscode","*.log","*.lock","package-lock.json","yarn.lock","bun.lockb","pnpm-lock.yaml","Cargo.lock","poetry.lock","Gemfile.lock","*.pyc","*.pyo","*.class","*.o","*.so","*.dylib","*.dll","*.exe"];async function ho(o){let t=await po();t.add(fo),t.add(G.ignore||[]);try{let e=j(o,".gitignore");if(_t(e)){let r=await mo(e,"utf-8");t.add(r)}}catch{}return t}async function ot(o){let t=await ho(o),e=new Set,r={total:0};await It(o,o,t,e,r);let n=Array.from(e).sort();return{version:"1.0",generatedAt:new Date().toISOString(),directories:n,directoryCount:n.length,fileCount:r.total}}async function It(o,t,e,r,n){let{readdir:i,stat:s}=await import("fs/promises"),a;try{a=await i(t)}catch{return}for(let d of a){let g=j(t,d),l=go(o,g);if(!(e.ignores(l)||e.ignores(l+"/")))try{let p=await s(g);p.isDirectory()?(r.add(l),await It(o,g,e,r,n)):p.isFile()&&n.total++}catch{}}}async function At(o){let t=j(o,".thinker.md","contexts"),e=[];return _t(j(o,".thinker.md","CONTEXT.md"))&&e.push(""),await Nt(t,"",e),e}async function Nt(o,t,e){let{readdir:r,stat:n}=await import("fs/promises"),i=t?j(o,t):o,s;try{s=await r(i)}catch{return}for(let a of s){let d=j(i,a),g=t?j(t,a):a;try{(await n(d)).isDirectory()?await Nt(o,g,e):a==="CONTEXT.md"&&e.push(uo(g))}catch{}}}async function m(o,t,e){let r=await $();if(r.mode!=="sandbox"||!r.callbackUrl||!r.jobToken)return;let n=r.callbackUrl.replace("/callback","/progress"),i={type:"progress",status:"processing",step:o,progress:t,message:e};try{let s=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${r.jobToken}`,"Content-Type":"application/json"},body:JSON.stringify(i)});s.ok||console.warn(`[Progress] Update failed: ${s.status} ${s.statusText}`)}catch(s){console.warn("[Progress] Update error:",s)}}async function Ot(o,t){let e=await $();if(e.mode!=="sandbox"||!e.callbackUrl||!e.jobToken)return;let r={jobId:e.jobId,status:"completed",artifactUrl:o,stats:t};try{let n=await fetch(e.callbackUrl,{method:"POST",headers:{Authorization:`Bearer ${e.jobToken}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok)throw console.error(`[Callback] Completion failed: ${n.status} ${n.statusText}`),new Error(`Callback failed: ${n.status}`);console.log("[Callback] Job completed successfully")}catch(n){throw console.error("[Callback] Completion error:",n),n}}async function Ft(o,t){let r=`# ${t||"Project Root"}
3
3
 
4
4
  <!--
5
5
  This context file should describe:
@@ -24,5 +24,5 @@ This context file should describe:
24
24
  ## Dependencies
25
25
 
26
26
  [Note any important dependencies or relationships]
27
- `;await Dt(o,r)}async function wo(o,t){let e=V(o),r=0,n=et($(o),"CONTEXT.md");await Ft(n,""),r++;for(let i of t){let s=et(e,i),a=et(s,"CONTEXT.md"),{mkdir:l}=await import("fs/promises");await l(s,{recursive:!0}),await Ft(a,i),r++}return r}async function jt(o){let t=yo(),e=process.cwd();try{await S(e)||(console.error(A.red("Error: Not a git repository")),console.log("Please run this command from the root of a git repository."),process.exit(1));let r=await P(e),n=$(r),{stat:i}=await import("fs/promises");try{await i(n),o.force||(console.error(A.yellow("Warning: .thinker.md/ already exists")),console.log("Use --force to reinitialize"),process.exit(1)),console.log(A.yellow("Reinitializing .thinker.md/..."))}catch{}await p("init",0,"Scanning repository structure..."),t.start("Scanning repository structure...");let s=await ot(r);t.succeed(`Found ${s.directoryCount} directories, ${s.fileCount} files`),await p("init",30,`Found ${s.directoryCount} directories`);let a=await I(r),l=await F(r),g=await K(r)||"unknown";t.start("Creating .thinker.md/ structure...");let{mkdir:d}=await import("fs/promises");await d(n,{recursive:!0}),await d(V(r),{recursive:!0});let f=await k(),h={...G,version:"1.0",projectId:f.metaRepoId||`local-${Date.now()}`,name:g,ignore:G.ignore||[],generatedAt:new Date().toISOString(),generatedBy:"tk-cli"};await tt(r,h),t.succeed("Created config.json"),await p("init",50,"Created config.json");let w={...s,commitSha:l,branch:a};await Dt(et(n,"structure.json"),JSON.stringify(w,null,2)),t.succeed("Created structure.json"),await p("init",60,"Created structure.json"),t.start("Creating empty CONTEXT.md files...");let c=await wo(r,s.directories);t.succeed(`Created ${c} empty CONTEXT.md files`),await p("init",100,`Created ${c} CONTEXT.md files`),console.log(),console.log(A.green("\u2713 Initialized .thinker.md/")),console.log(),console.log("Created:"),console.log(` ${A.cyan("config.json")} - Project configuration`),console.log(` ${A.cyan("structure.json")} - Directory structure`),console.log(` ${A.cyan(`${c} CONTEXT.md`)} files - Ready for documentation`),console.log(),console.log("Next steps:"),console.log(" 1. Fill in the CONTEXT.md files with documentation"),console.log(" 2. Run "+A.cyan("tk validate")+" to check structure"),console.log(" 3. Run "+A.cyan("tk push")+" to upload to storage")}catch(r){t.fail("Initialization failed"),console.error(A.red(`Error: ${r}`)),process.exit(1)}}import{join as at}from"path";import D from"chalk";import Co from"ora";async function ct(o){let t=[],e=[],r=0,n=$(o),i=V(o),{stat:s,readFile:a}=await import("fs/promises");try{await s(n)}catch{return t.push({type:"structure_mismatch",path:m,message:`${m}/ directory not found. Run 'tk init' first.`}),{valid:!1,errors:t,warnings:e,validContextCount:0,directoryCount:0}}let l=await _(o);l?(l.version||t.push({type:"invalid_config",path:`${m}/${b}`,message:`${b} missing required field: version`}),l.projectId||t.push({type:"invalid_config",path:`${m}/${b}`,message:`${b} missing required field: projectId`})):t.push({type:"invalid_config",path:`${m}/${b}`,message:`${b} not found or invalid`});let g=null;try{let c=at(n,q),T=await a(c,"utf-8");g=JSON.parse(T)}catch{t.push({type:"structure_mismatch",path:`${m}/${q}`,message:`${q} not found or invalid`})}let d=await ot(o),f=at(n,v);try{await s(f),r++}catch{t.push({type:"missing_context",path:v,message:`Root ${v} not found`})}for(let c of d.directories){let T=at(i,c,v);try{if((await s(T)).isFile()){r++;let C=await a(T,"utf-8");(C.trim().length<kt||C.includes(Tt))&&e.push({type:"empty_context",path:`${c}/${v}`,message:"Context file appears to be empty or contains only template"})}}catch{t.push({type:"missing_context",path:`${c}/${v}`,message:`Missing context file for directory: ${c}`})}}let h=await At(o),w=new Set(d.directories);for(let c of h)c===""||c==="."||w.has(c)||e.push({type:"orphan_context",path:`${c}/${v}`,message:`Context exists for non-existent directory: ${c}`});return{valid:t.length===0,errors:t,warnings:e,validContextCount:r,directoryCount:d.directoryCount}}async function Lt(o){let t=Co(),e=process.cwd();try{await S(e)||(console.error(D.red("Error: Not a git repository")),process.exit(1));let r=await P(e);await p("validate",0,"Validating structure..."),t.start(`Validating ${m}/ structure...`);let n=await ct(r);if(t.stop(),await p("validate",100,"Validation complete"),!o.quiet){if(console.log(),n.errors.length>0){console.log(D.red(`\u2717 ${n.errors.length} error(s):`));for(let i of n.errors)console.log(D.red(` \u2022 ${i.path}: ${i.message}`));console.log()}if(n.warnings.length>0){console.log(D.yellow(`\u26A0 ${n.warnings.length} warning(s):`));for(let i of n.warnings)console.log(D.yellow(` \u2022 ${i.path}: ${i.message}`));console.log()}n.valid?console.log(D.green(`\u2713 Validation passed: ${n.validContextCount} contexts valid`)):console.log(D.red(`\u2717 Validation failed: ${n.errors.length} error(s), ${n.warnings.length} warning(s)`))}n.valid||process.exit(1)}catch(r){t.fail("Validation failed"),console.error(D.red(`Error: ${r}`)),process.exit(1)}}import{join as Gt}from"path";import{writeFile as $o}from"fs/promises";import N from"chalk";import Po from"ora";import*as Vt from"tar";import{S3Client as xo,PutObjectCommand as dt,GetObjectCommand as Mt}from"@aws-sdk/client-s3";var lt=null;function X(){if(!lt){let o=process.env.R2_ENDPOINT,t=process.env.R2_ACCESS_KEY_ID,e=process.env.R2_SECRET_ACCESS_KEY;if(!o||!t||!e)throw new Error("R2 credentials not configured. Required: R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY");lt=new xo({region:"auto",endpoint:o,credentials:{accessKeyId:t,secretAccessKey:e}})}return lt}function z(){return process.env.R2_BUCKET||ht}async function H(o,t,e=xt,r=Et){let n;for(let i=1;i<=e;i++)try{return await o()}catch(s){if(n=s instanceof Error?s:new Error(String(s)),n.name==="NoSuchKey"||n.message.includes("credentials")||n.message.includes("AccessDenied"))throw n;if(i<e){let a=r*Math.pow(2,i-1);console.log(`[Storage] ${t} failed (attempt ${i}/${e}), retrying in ${a}ms...`),await new Promise(l=>setTimeout(l,a))}}throw n}function Ut(o,t,e){return`${W}/${o}/${Q}/${t}/${e}/${pt}`}function Eo(o,t,e){return`${W}/${o}/${Q}/${t}/${e}/${mt}`}function Bt(o,t){return`${W}/${o}/${Q}/${t}/${ft}`}async function To(o,t,e,r){let n=X(),i=Ut(o,t,e);return await H(()=>n.send(new dt({Bucket:z(),Key:i,Body:r,ContentType:"application/gzip"})),"uploadArchive"),console.log(`[Storage] Uploaded archive to ${i}`),i}async function ko(o,t,e,r){let n=X(),i=Eo(o,t,e);await H(()=>n.send(new dt({Bucket:z(),Key:i,Body:JSON.stringify(r,null,2),ContentType:"application/json"})),"uploadMetadata"),console.log(`[Storage] Uploaded metadata to ${i}`)}async function Ro(o,t,e){let r=X(),n=Bt(o,t);await H(()=>r.send(new dt({Bucket:z(),Key:n,Body:e,ContentType:"text/plain"})),"updateLatestPointer"),console.log(`[Storage] Updated latest pointer for ${t} -> ${e}`)}async function rt(o,t){let e=X(),r=Bt(o,t);try{return(await(await H(()=>e.send(new Mt({Bucket:z(),Key:r})),"getLatestCommitSha")).Body?.transformToString())?.trim()||null}catch(n){if(n instanceof Error&&"name"in n&&n.name==="NoSuchKey")return null;throw n}}async function Kt(o,t,e){let r=X(),n=Ut(o,t,e);try{let s=await(await H(()=>r.send(new Mt({Bucket:z(),Key:n})),"downloadArchive")).Body?.transformToByteArray();return s?Buffer.from(s):null}catch(i){if(i instanceof Error&&"name"in i&&i.name==="NoSuchKey")return null;throw i}}async function Jt(o,t,e,r,n){let i=await To(o,t,e,r);return await ko(o,t,e,n),await Ro(o,t,e),i}async function So(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let a of s){let l=Gt(i,a);try{(await e(l)).isDirectory()?await n(l):a==="CONTEXT.md"&&r++}catch{}}}return await n(o),r}async function Xt(o){let t=Po(),e=process.cwd();try{await S(e)||(console.error(N.red("Error: Not a git repository")),process.exit(1));let r=await P(e),n=await k(),i;if(n.mode==="sandbox"&&n.metaRepoId)i=n.metaRepoId;else{let c=await _(r);c?.projectId?i=c.projectId:(console.error(N.red("Error: No project ID found. Run 'tk init' first or set META_REPO_ID.")),process.exit(1))}let s=o.branch||n.branch||await I(r);await p("pull",0,`Fetching latest context for branch '${s}'...`),console.log(`Fetching latest context for branch '${N.cyan(s)}'...`),t.start("Looking up latest version...");let a;if(o.commit)a=o.commit,t.succeed(`Using specified commit: ${a.slice(0,7)}`);else{try{a=await rt(i,s)}catch(c){throw c instanceof Error&&c.message.includes("R2 credentials")&&(t.fail("R2 credentials not configured"),console.error(N.red(c.message)),process.exit(1)),c}a||(t.info("No previous context found for this branch"),console.log(`
28
- This appears to be the first generation. Use 'tk init' to create initial structure.`),process.exit(0)),t.succeed(`Found version: ${a.slice(0,7)}`)}await p("pull",30,`Found version: ${a.slice(0,7)}`),t.start("Downloading thinker.md.tar.gz...");let l=await Kt(i,s,a);l||(t.fail("Archive not found"),console.error(N.red(`No archive found for commit ${a.slice(0,7)}`)),process.exit(1)),t.succeed(`Downloaded archive: ${l.length} bytes`),await p("pull",60,"Extracting archive...");let g=Gt(r,".thinker.md.tar.gz");await $o(g,l),t.start("Extracting to .thinker.md/...");let d=$(r),{rm:f}=await import("fs/promises");try{await f(d,{recursive:!0,force:!0})}catch{}await Vt.extract({file:g,cwd:r}),await f(g,{force:!0}),t.succeed("Extracted .thinker.md/"),await p("pull",80,"Updating config...");let h=await _(r);h&&(h.pulledFrom=a,h.pulledAt=new Date().toISOString(),await tt(r,h));let w=await So(d);await p("pull",100,`Pulled ${w} context files`),console.log(),console.log(N.green(`\u2713 Pulled ${w} context files`)),console.log(),console.log(`From: ${N.cyan(`${s}@${a.slice(0,7)}`)}`),console.log(),console.log("Next steps:"),console.log(" 1. Review and update context files as needed"),console.log(" 2. Run "+N.cyan("tk validate")+" to check structure"),console.log(" 3. Run "+N.cyan("tk push")+" to upload changes")}catch(r){t.fail("Pull failed"),console.error(N.red(`Error: ${r}`)),process.exit(1)}}import{join as zt}from"path";import E from"chalk";import bo from"ora";import*as Ht from"tar";async function vo(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0,n=0;async function i(s){let a;try{a=await t(s)}catch{return}for(let l of a){let g=zt(s,l);try{(await e(g)).isDirectory()?await i(g):(n++,l===v&&r++)}catch{}}}return await i(o),{contextFiles:r,totalFiles:n}}async function Yt(o){let t=bo(),e=process.cwd();try{if(it()==="sandbox")try{bt()}catch(x){console.error(E.red(`Sandbox environment error: ${x}`)),process.exit(1)}await S(e)||(console.error(E.red("Error: Not a git repository")),process.exit(1));let r=await P(e),n=await k(),i=$(r),{stat:s,rm:a}=await import("fs/promises");try{await s(i)}catch{console.error(E.red(`Error: ${m}/ not found`)),console.log("Run 'tk init' first to create the structure."),process.exit(1)}if(await p("push",0,"Validating structure..."),!o.skipValidation){t.start(`Validating ${m}/ structure...`);let x=await ct(r);if(!x.valid){t.fail("Validation failed"),console.log(),console.log(E.red("Fix the following errors before pushing:"));for(let Y of x.errors)console.log(E.red(` \u2022 ${Y.path}: ${Y.message}`));process.exit(1)}t.succeed("Validation passed"),x.warnings.length>0&&console.log(E.yellow(` ${x.warnings.length} warning(s)`))}await p("push",20,"Validation passed");let l;if(n.mode==="sandbox"&&n.metaRepoId)l=n.metaRepoId;else{let x=await _(r);x?.projectId?l=x.projectId:(console.error(E.red("Error: No project ID found. Set META_REPO_ID environment variable.")),process.exit(1))}let g=n.branch||await I(r),d=n.commitSha||await F(r),f=await vt(r,d);await p("push",30,"Packaging .thinker.md/..."),t.start(`Packaging ${m}/...`);let h=zt(r,`${m}.tar.gz`);await Ht.create({file:h,cwd:r,gzip:!0},[m]);let{readFile:w}=await import("fs/promises"),c=await w(h);await a(h,{force:!0}),t.succeed(`Packaged archive: ${c.length} bytes`),await p("push",60,"Uploading to storage...");let T=await vo(i),L=await _(r),C=o.forceFull?nt:L?.pulledFrom?wt:nt,ut={branch:g,commitSha:d,commitMessage:f,mode:C,parentCommitSha:L?.pulledFrom,generatedAt:new Date().toISOString(),jobId:n.jobId||`local-${Date.now()}`,tokensUsed:0,filesAnalyzed:T.totalFiles,contextFilesGenerated:T.contextFiles};t.start("Uploading to storage...");try{let x=await Jt(l,g,d,c,ut);if(t.succeed("Uploaded to storage"),await p("push",90,"Finalizing..."),n.mode==="sandbox"&&n.callbackUrl){let Y={filesAnalyzed:T.totalFiles,contextFilesGenerated:T.contextFiles,tokensUsed:0,mode:C};await Ot(x,Y)}await p("push",100,"Done!"),console.log(),console.log(E.green("\u2713 Successfully pushed to storage")),console.log(),console.log(`Branch: ${E.cyan(g)}`),console.log(`Commit: ${E.cyan(d.slice(0,7))}`),console.log(`Mode: ${E.cyan(C)}`),console.log(`Context files: ${E.cyan(T.contextFiles)}`),n.mode==="sandbox"&&(console.log(),console.log(E.green("\u2713 Job completed")))}catch(x){t.fail("Upload failed"),x instanceof Error&&x.message.includes("R2 credentials")?(console.error(E.red(x.message)),console.log(),console.log("Required environment variables:"),console.log(" R2_ENDPOINT"),console.log(" R2_ACCESS_KEY_ID"),console.log(" R2_SECRET_ACCESS_KEY"),console.log(" R2_BUCKET")):console.error(E.red(`Error: ${x}`)),process.exit(1)}}catch(r){t.fail("Push failed"),console.error(E.red(`Error: ${r}`)),process.exit(1)}}import O from"chalk";import qt from"ora";async function _o(o,t){try{let e=await fetch(`${o}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return e.ok?{valid:!0,userId:(await e.json()).userId}:e.status===401?{valid:!1,error:"Invalid or expired token"}:{valid:!1,error:`API error: ${e.status} ${e.statusText}`}}catch(e){return{valid:!1,error:`Connection failed: ${e}`}}}async function Wt(o){let t=qt(),e=o.apiUrl||Z;if(o.token){t.start("Validating token...");let n=await _o(e,o.token);n.valid||(t.fail("Authentication failed"),console.error(O.red(`Error: ${n.error}`)),process.exit(1));let i={apiToken:o.token,apiUrl:e,userId:n.userId};await st(i),t.succeed("Authenticated successfully"),console.log(),console.log(`Config saved to: ${O.cyan(M)}`);return}console.log(O.bold("ThinkerMD Login")),console.log(),console.log("To authenticate, you need an API token from ThinkerMD."),console.log(),console.log("Get your token at: "+O.cyan(`${e}/settings/tokens`)),console.log(),console.log("Then run:"),console.log(O.cyan(" tk login --token YOUR_TOKEN")),console.log();let r=await J();r.apiToken&&(console.log(O.green("You are already logged in.")),console.log(`User ID: ${O.cyan(r.userId||"unknown")}`),console.log(),console.log("To re-authenticate, run:"),console.log(O.cyan(" tk login --token NEW_TOKEN")))}async function Qt(){let o=qt();o.start("Logging out..."),await st({}),o.succeed("Logged out successfully"),console.log(),console.log(`Config cleared from: ${O.cyan(M)}`)}import{join as Zt}from"path";import u from"chalk";import Io from"ora";async function Ao(o){let{readFile:t}=await import("fs/promises"),{existsSync:e}=await import("fs"),r=Zt($(o),"structure.json");try{if(e(r)){let n=await t(r,"utf-8");return JSON.parse(n)}}catch{}return null}async function No(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let a of s){let l=Zt(i,a);try{(await e(l)).isDirectory()?await n(l):a==="CONTEXT.md"&&r++}catch{}}}return await n(o),r}async function to(){let o=Io(),t=process.cwd();try{await S(t)||(console.error(u.red("Error: Not a git repository")),process.exit(1));let e=await P(t),r=await k(),n=$(e);console.log(u.bold("ThinkerMD Status")),console.log();let i=await K(e),s=r.branch||await I(e),a=r.commitSha||await F(e);console.log(u.dim("Repository:")),console.log(` Name: ${u.cyan(i||"unknown")}`),console.log(` Branch: ${u.cyan(s)}`),console.log(` Commit: ${u.cyan(a.slice(0,7))}`),console.log();let{stat:l}=await import("fs/promises"),g=!1;try{await l(n),g=!0}catch{}if(!g){console.log(u.dim("Local Context:")),console.log(` Status: ${u.yellow("Not initialized")}`),console.log(),console.log("Run "+u.cyan("tk init")+" to create .thinker.md/");return}console.log(u.dim("Local Context:"));let d=await _(e),f=await Ao(e),h=await No(n);if(console.log(` Status: ${u.green("Initialized")}`),console.log(` Project ID: ${u.cyan(d?.projectId||"unknown")}`),console.log(` Context files: ${u.cyan(h)}`),f&&(console.log(` Directories: ${u.cyan(f.directoryCount)}`),console.log(` Files tracked: ${u.cyan(f.fileCount)}`)),d?.generatedAt){let c=new Date(d.generatedAt);console.log(` Generated: ${u.cyan(c.toLocaleString())}`)}d?.pulledFrom&&console.log(` Pulled from: ${u.cyan(d.pulledFrom.slice(0,7))}`),console.log();let w=r.metaRepoId||d?.projectId;if(w&&r.r2Endpoint){console.log(u.dim("Remote Storage:")),o.start("Checking remote...");try{let c=await rt(w,s);c?(o.stop(),console.log(` Latest commit: ${u.cyan(c.slice(0,7))}`),c===a?console.log(` Status: ${u.green("Up to date")}`):d?.pulledFrom===c?console.log(` Status: ${u.yellow("Local changes not pushed")}`):(console.log(` Status: ${u.yellow("Remote has newer version")}`),console.log(),console.log("Run "+u.cyan("tk pull")+" to download latest context"))):(o.stop(),console.log(` Status: ${u.yellow("No remote context found")}`),console.log(),console.log("Run "+u.cyan("tk push")+" to upload context"))}catch{o.stop(),console.log(` Status: ${u.dim("Unable to check (no credentials)")}`)}}console.log(),console.log(u.dim("Mode:")),console.log(` ${r.mode==="sandbox"?u.cyan("Sandbox"):u.cyan("Local")}`),r.mode==="sandbox"&&console.log(` Job ID: ${u.cyan(r.jobId||"unknown")}`)}catch(e){o.fail("Failed to get status"),console.error(u.red(`Error: ${e}`)),process.exit(1)}}import y from"chalk";import Oo from"ora";async function oo(o){let t=Oo(),e=process.cwd();try{await S(e)||(console.error(y.red("Error: Not a git repository")),process.exit(1));let r=await P(e);if((await k()).mode==="sandbox"){console.error(y.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 J();i.apiToken||(console.error(y.red("Error: Not logged in")),console.log(),console.log("Please authenticate first:"),console.log(y.cyan(" tk login --token YOUR_TOKEN")),process.exit(1));let s=i.apiUrl||Z,a=await K(r),l=o.branch||await I(r),g=await F(r);a||(console.error(y.red("Error: Unable to determine repository name")),console.log("Make sure this repository has a remote origin configured."),process.exit(1)),console.log(y.bold("Trigger Context Generation")),console.log(),console.log(`Repository: ${y.cyan(a)}`),console.log(`Branch: ${y.cyan(l)}`),console.log(`Commit: ${y.cyan(g.slice(0,7))}`),console.log(),t.start("Looking up meta-repository...");let d=await fetch(`${s}/api/meta-repositories/lookup?repo=${encodeURIComponent(a)}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});d.ok||(d.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(y.cyan(`${s}/dashboard`)),process.exit(1)),t.fail("API error"),console.error(y.red(`Error: ${d.status} ${d.statusText}`)),process.exit(1));let f=await d.json();t.succeed(`Found meta-repository: ${f.id}`),t.start("Triggering generation job...");let h=await fetch(`${s}/api/jobs/generate`,{method:"POST",headers:{Authorization:`Bearer ${i.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({metaRepositoryId:f.id,commitSha:g,branch:l,priority:"normal"})});if(!h.ok){t.fail("Failed to trigger generation");let c=await h.json().catch(()=>({}));console.error(y.red(`Error: ${c.error||h.statusText}`)),process.exit(1)}let w=await h.json();if(t.succeed("Generation job queued"),console.log(),console.log(`Job ID: ${y.cyan(w.jobId)}`),console.log(`Status: ${y.cyan(w.status)}`),w.estimatedPosition&&console.log(`Queue position: ${y.cyan(w.estimatedPosition)}`),console.log(),console.log("Track progress at:"),console.log(y.cyan(`${s}/dashboard/repositories/${f.id}/jobs/${w.jobId}`)),o.wait){console.log(),t.start("Waiting for completion...");let c=!1,T="";for(;!c;){await new Promise(C=>setTimeout(C,5e3));let L=await fetch(`${s}/api/jobs/${w.jobId}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});if(L.ok){let C=await L.json();C.status!==T&&(T=C.status,t.text=`Status: ${C.status} (${C.progress||0}%)`),(C.status==="completed"||C.status==="failed")&&(c=!0,C.status==="completed"?(t.succeed("Generation completed!"),console.log(),console.log("Run "+y.cyan("tk pull")+" to download the generated context")):(t.fail("Generation failed"),C.errorMessage&&console.error(y.red(`Error: ${C.errorMessage}`))))}}}}catch(r){t.fail("Failed to trigger generation"),console.error(y.red(`Error: ${r}`)),process.exit(1)}}var R=new Fo;R.name("tk").description("ThinkerMD CLI - Manage .thinker.md/ context files").version("0.1.0");R.command("init").description("Create .thinker.md/ structure with empty CONTEXT.md files").option("-f, --force","Reinitialize even if .thinker.md/ exists").action(o=>jt(o));R.command("validate").description("Verify .thinker.md/ structure matches repository").option("-q, --quiet","Only output errors, no summary").action(o=>Lt(o));R.command("pull").description("Download previous context from storage").option("-b, --branch <branch>","Specify branch to pull from").option("-c, --commit <sha>","Pull specific commit version").action(o=>Xt(o));R.command("push").description("Validate and upload .thinker.md/ to storage").option("--skip-validation","Skip validation before push").option("--force-full","Force full generation mode (ignore previous context)").action(o=>Yt(o));R.command("login").description("Authenticate with ThinkerMD API").option("-t, --token <token>","API token").option("--api-url <url>","Custom API URL").action(o=>Wt(o));R.command("logout").description("Remove saved authentication").action(()=>Qt());R.command("status").description("Show status of .thinker.md/ context").action(()=>to());R.command("generate").description("Trigger context generation via API").option("-b, --branch <branch>","Specify branch to generate for").option("-w, --wait","Wait for generation to complete").action(o=>oo(o));R.on("command:*",()=>{console.error(eo.red(`Unknown command: ${R.args.join(" ")}`)),console.log(),R.help()});async function ro(){try{await R.parseAsync(process.argv)}catch(o){o instanceof Error&&console.error(eo.red(`Error: ${o.message}`)),process.exit(1)}}ro().catch(o=>{console.error("Fatal error:",o),process.exit(1)});
27
+ `;await Dt(o,r)}async function wo(o,t,e=!1){let{existsSync:r}=await import("fs"),n=V(o),i=0,s=0,a=et(P(o),"CONTEXT.md");e&&r(a)?s++:(await Ft(a,""),i++);for(let d of t){let g=et(n,d),l=et(g,"CONTEXT.md"),{mkdir:p}=await import("fs/promises");if(await p(g,{recursive:!0}),e&&r(l)){s++;continue}await Ft(l,d),i++}return{created:i,skipped:s}}async function jt(o){let t=yo(),e=process.cwd();try{await b(e)||(console.error(k.red("Error: Not a git repository")),console.log("Please run this command from the root of a git repository."),process.exit(1));let r=await S(e),n=P(r),{stat:i}=await import("fs/promises");try{await i(n),!o.force&&!o.merge&&(console.error(k.yellow("Warning: .thinker.md/ already exists")),console.log("Use --force to reinitialize or --merge to add missing contexts"),process.exit(1)),o.merge?console.log(k.cyan("Merging: adding missing CONTEXT.md files...")):console.log(k.yellow("Reinitializing .thinker.md/..."))}catch{}await m("init",0,"Scanning repository structure..."),t.start("Scanning repository structure...");let s=await ot(r);t.succeed(`Found ${s.directoryCount} directories, ${s.fileCount} files`),await m("init",30,`Found ${s.directoryCount} directories`);let a=await A(r),d=await F(r),g=await K(r)||"unknown";t.start("Creating .thinker.md/ structure...");let{mkdir:l}=await import("fs/promises");await l(n,{recursive:!0}),await l(V(r),{recursive:!0});let p=await $(),h={...G,version:"1.0",projectId:p.metaRepoId||`local-${Date.now()}`,name:g,ignore:G.ignore||[],generatedAt:new Date().toISOString(),generatedBy:"tk-cli"};await tt(r,h),t.succeed("Created config.json"),await m("init",50,"Created config.json");let w={...s,commitSha:d,branch:a};await Dt(et(n,"structure.json"),JSON.stringify(w,null,2)),t.succeed("Created structure.json"),await m("init",60,"Created structure.json"),t.start(o.merge?"Adding missing CONTEXT.md files...":"Creating empty CONTEXT.md files...");let{created:c,skipped:x}=await wo(r,s.directories,o.merge);o.merge?(t.succeed(`Added ${c} new CONTEXT.md files (${x} existing skipped)`),await m("init",100,`Added ${c} new CONTEXT.md files`)):(t.succeed(`Created ${c} empty CONTEXT.md files`),await m("init",100,`Created ${c} CONTEXT.md files`)),console.log(),o.merge?(console.log(k.green("\u2713 Merged .thinker.md/")),console.log(),console.log(`Added: ${k.cyan(`${c}`)} new CONTEXT.md files`),console.log(`Skipped: ${k.gray(`${x}`)} existing files`)):(console.log(k.green("\u2713 Initialized .thinker.md/")),console.log(),console.log("Created:"),console.log(` ${k.cyan("config.json")} - Project configuration`),console.log(` ${k.cyan("structure.json")} - Directory structure`),console.log(` ${k.cyan(`${c} CONTEXT.md`)} files - Ready for documentation`)),console.log(),console.log("Next steps:"),console.log(" 1. Fill in the CONTEXT.md files with documentation"),console.log(" 2. Run "+k.cyan("tk validate")+" to check structure"),console.log(" 3. Run "+k.cyan("tk push")+" to upload to storage")}catch(r){t.fail("Initialization failed"),console.error(k.red(`Error: ${r}`)),process.exit(1)}}import{join as at}from"path";import D from"chalk";import Co from"ora";async function ct(o){let t=[],e=[],r=0,n=P(o),i=V(o),{stat:s,readFile:a}=await import("fs/promises");try{await s(n)}catch{return t.push({type:"structure_mismatch",path:f,message:`${f}/ directory not found. Run 'tk init' first.`}),{valid:!1,errors:t,warnings:e,validContextCount:0,directoryCount:0}}let d=await I(o);d?(d.version||t.push({type:"invalid_config",path:`${f}/${v}`,message:`${v} missing required field: version`}),d.projectId||t.push({type:"invalid_config",path:`${f}/${v}`,message:`${v} missing required field: projectId`})):t.push({type:"invalid_config",path:`${f}/${v}`,message:`${v} not found or invalid`});let g=null;try{let c=at(n,q),x=await a(c,"utf-8");g=JSON.parse(x)}catch{t.push({type:"structure_mismatch",path:`${f}/${q}`,message:`${q} not found or invalid`})}let l=await ot(o),p=at(n,_);try{await s(p),r++}catch{t.push({type:"missing_context",path:_,message:`Root ${_} not found`})}for(let c of l.directories){let x=at(i,c,_);try{if((await s(x)).isFile()){r++;let C=await a(x,"utf-8");(C.trim().length<kt||C.includes(Tt))&&e.push({type:"empty_context",path:`${c}/${_}`,message:"Context file appears to be empty or contains only template"})}}catch{t.push({type:"missing_context",path:`${c}/${_}`,message:`Missing context file for directory: ${c}`})}}let h=await At(o),w=new Set(l.directories);for(let c of h)c===""||c==="."||w.has(c)||e.push({type:"orphan_context",path:`${c}/${_}`,message:`Context exists for non-existent directory: ${c}`});return{valid:t.length===0,errors:t,warnings:e,validContextCount:r,directoryCount:l.directoryCount}}async function Lt(o){let t=Co(),e=process.cwd();try{await b(e)||(console.error(D.red("Error: Not a git repository")),process.exit(1));let r=await S(e);await m("validate",0,"Validating structure..."),t.start(`Validating ${f}/ structure...`);let n=await ct(r);if(t.stop(),await m("validate",100,"Validation complete"),!o.quiet){if(console.log(),n.errors.length>0){console.log(D.red(`\u2717 ${n.errors.length} error(s):`));for(let i of n.errors)console.log(D.red(` \u2022 ${i.path}: ${i.message}`));console.log()}if(n.warnings.length>0){console.log(D.yellow(`\u26A0 ${n.warnings.length} warning(s):`));for(let i of n.warnings)console.log(D.yellow(` \u2022 ${i.path}: ${i.message}`));console.log()}n.valid?console.log(D.green(`\u2713 Validation passed: ${n.validContextCount} contexts valid`)):console.log(D.red(`\u2717 Validation failed: ${n.errors.length} error(s), ${n.warnings.length} warning(s)`))}n.valid||process.exit(1)}catch(r){t.fail("Validation failed"),console.error(D.red(`Error: ${r}`)),process.exit(1)}}import{join as Gt}from"path";import{writeFile as Ro}from"fs/promises";import N from"chalk";import Po from"ora";import*as Vt from"tar";import{S3Client as xo,PutObjectCommand as dt,GetObjectCommand as Mt}from"@aws-sdk/client-s3";var lt=null;function X(){if(!lt){let o=process.env.R2_ENDPOINT,t=process.env.R2_ACCESS_KEY_ID,e=process.env.R2_SECRET_ACCESS_KEY;if(!o||!t||!e)throw new Error("R2 credentials not configured. Required: R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY");lt=new xo({region:"auto",endpoint:o,credentials:{accessKeyId:t,secretAccessKey:e}})}return lt}function z(){return process.env.R2_BUCKET||ht}async function H(o,t,e=xt,r=Et){let n;for(let i=1;i<=e;i++)try{return await o()}catch(s){if(n=s instanceof Error?s:new Error(String(s)),n.name==="NoSuchKey"||n.message.includes("credentials")||n.message.includes("AccessDenied"))throw n;if(i<e){let a=r*Math.pow(2,i-1);console.log(`[Storage] ${t} failed (attempt ${i}/${e}), retrying in ${a}ms...`),await new Promise(d=>setTimeout(d,a))}}throw n}function Ut(o,t,e){return`${W}/${o}/${Q}/${t}/${e}/${mt}`}function Eo(o,t,e){return`${W}/${o}/${Q}/${t}/${e}/${pt}`}function Bt(o,t){return`${W}/${o}/${Q}/${t}/${ft}`}async function To(o,t,e,r){let n=X(),i=Ut(o,t,e);return await H(()=>n.send(new dt({Bucket:z(),Key:i,Body:r,ContentType:"application/gzip"})),"uploadArchive"),console.log(`[Storage] Uploaded archive to ${i}`),i}async function ko(o,t,e,r){let n=X(),i=Eo(o,t,e);await H(()=>n.send(new dt({Bucket:z(),Key:i,Body:JSON.stringify(r,null,2),ContentType:"application/json"})),"uploadMetadata"),console.log(`[Storage] Uploaded metadata to ${i}`)}async function $o(o,t,e){let r=X(),n=Bt(o,t);await H(()=>r.send(new dt({Bucket:z(),Key:n,Body:e,ContentType:"text/plain"})),"updateLatestPointer"),console.log(`[Storage] Updated latest pointer for ${t} -> ${e}`)}async function rt(o,t){let e=X(),r=Bt(o,t);try{return(await(await H(()=>e.send(new Mt({Bucket:z(),Key:r})),"getLatestCommitSha")).Body?.transformToString())?.trim()||null}catch(n){if(n instanceof Error&&"name"in n&&n.name==="NoSuchKey")return null;throw n}}async function Kt(o,t,e){let r=X(),n=Ut(o,t,e);try{let s=await(await H(()=>r.send(new Mt({Bucket:z(),Key:n})),"downloadArchive")).Body?.transformToByteArray();return s?Buffer.from(s):null}catch(i){if(i instanceof Error&&"name"in i&&i.name==="NoSuchKey")return null;throw i}}async function Jt(o,t,e,r,n){let i=await To(o,t,e,r);return await ko(o,t,e,n),await $o(o,t,e),i}async function So(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let a of s){let d=Gt(i,a);try{(await e(d)).isDirectory()?await n(d):a==="CONTEXT.md"&&r++}catch{}}}return await n(o),r}async function Xt(o){let t=Po(),e=process.cwd();try{await b(e)||(console.error(N.red("Error: Not a git repository")),process.exit(1));let r=await S(e),n=await $(),i;if(n.mode==="sandbox"&&n.metaRepoId)i=n.metaRepoId;else{let c=await I(r);c?.projectId?i=c.projectId:(console.error(N.red("Error: No project ID found. Run 'tk init' first or set META_REPO_ID.")),process.exit(1))}let s=o.branch||n.branch||await A(r);await m("pull",0,`Fetching latest context for branch '${s}'...`),console.log(`Fetching latest context for branch '${N.cyan(s)}'...`),t.start("Looking up latest version...");let a;if(o.commit)a=o.commit,t.succeed(`Using specified commit: ${a.slice(0,7)}`);else{try{a=await rt(i,s)}catch(c){throw c instanceof Error&&c.message.includes("R2 credentials")&&(t.fail("R2 credentials not configured"),console.error(N.red(c.message)),process.exit(1)),c}a||(t.info("No previous context found for this branch"),console.log(`
28
+ This appears to be the first generation. Use 'tk init' to create initial structure.`),process.exit(0)),t.succeed(`Found version: ${a.slice(0,7)}`)}await m("pull",30,`Found version: ${a.slice(0,7)}`),t.start("Downloading thinker.md.tar.gz...");let d=await Kt(i,s,a);d||(t.fail("Archive not found"),console.error(N.red(`No archive found for commit ${a.slice(0,7)}`)),process.exit(1)),t.succeed(`Downloaded archive: ${d.length} bytes`),await m("pull",60,"Extracting archive...");let g=Gt(r,".thinker.md.tar.gz");await Ro(g,d),t.start("Extracting to .thinker.md/...");let l=P(r),{rm:p}=await import("fs/promises");try{await p(l,{recursive:!0,force:!0})}catch{}await Vt.extract({file:g,cwd:r}),await p(g,{force:!0}),t.succeed("Extracted .thinker.md/"),await m("pull",80,"Updating config...");let h=await I(r);h&&(h.pulledFrom=a,h.pulledAt=new Date().toISOString(),await tt(r,h));let w=await So(l);await m("pull",100,`Pulled ${w} context files`),console.log(),console.log(N.green(`\u2713 Pulled ${w} context files`)),console.log(),console.log(`From: ${N.cyan(`${s}@${a.slice(0,7)}`)}`),console.log(),console.log("Next steps:"),console.log(" 1. Review and update context files as needed"),console.log(" 2. Run "+N.cyan("tk validate")+" to check structure"),console.log(" 3. Run "+N.cyan("tk push")+" to upload changes")}catch(r){t.fail("Pull failed"),console.error(N.red(`Error: ${r}`)),process.exit(1)}}import{join as zt}from"path";import T from"chalk";import bo from"ora";import*as Ht from"tar";async function vo(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0,n=0;async function i(s){let a;try{a=await t(s)}catch{return}for(let d of a){let g=zt(s,d);try{(await e(g)).isDirectory()?await i(g):(n++,d===_&&r++)}catch{}}}return await i(o),{contextFiles:r,totalFiles:n}}async function Yt(o){let t=bo(),e=process.cwd();try{if(it()==="sandbox")try{bt()}catch(E){console.error(T.red(`Sandbox environment error: ${E}`)),process.exit(1)}await b(e)||(console.error(T.red("Error: Not a git repository")),process.exit(1));let r=await S(e),n=await $(),i=P(r),{stat:s,rm:a}=await import("fs/promises");try{await s(i)}catch{console.error(T.red(`Error: ${f}/ not found`)),console.log("Run 'tk init' first to create the structure."),process.exit(1)}if(await m("push",0,"Validating structure..."),!o.skipValidation){t.start(`Validating ${f}/ structure...`);let E=await ct(r);if(!E.valid){t.fail("Validation failed"),console.log(),console.log(T.red("Fix the following errors before pushing:"));for(let Y of E.errors)console.log(T.red(` \u2022 ${Y.path}: ${Y.message}`));process.exit(1)}t.succeed("Validation passed"),E.warnings.length>0&&console.log(T.yellow(` ${E.warnings.length} warning(s)`))}await m("push",20,"Validation passed");let d;if(n.mode==="sandbox"&&n.metaRepoId)d=n.metaRepoId;else{let E=await I(r);E?.projectId?d=E.projectId:(console.error(T.red("Error: No project ID found. Set META_REPO_ID environment variable.")),process.exit(1))}let g=n.branch||await A(r),l=n.commitSha||await F(r),p=await vt(r,l);await m("push",30,"Packaging .thinker.md/..."),t.start(`Packaging ${f}/...`);let h=zt(r,`${f}.tar.gz`);await Ht.create({file:h,cwd:r,gzip:!0},[f]);let{readFile:w}=await import("fs/promises"),c=await w(h);await a(h,{force:!0}),t.succeed(`Packaged archive: ${c.length} bytes`),await m("push",60,"Uploading to storage...");let x=await vo(i),L=await I(r),C=o.forceFull?nt:L?.pulledFrom?wt:nt,gt={branch:g,commitSha:l,commitMessage:p,mode:C,parentCommitSha:L?.pulledFrom,generatedAt:new Date().toISOString(),jobId:n.jobId||`local-${Date.now()}`,tokensUsed:0,filesAnalyzed:x.totalFiles,contextFilesGenerated:x.contextFiles};t.start("Uploading to storage...");try{let E=await Jt(d,g,l,c,gt);if(t.succeed("Uploaded to storage"),await m("push",90,"Finalizing..."),n.mode==="sandbox"&&n.callbackUrl){let Y={filesAnalyzed:x.totalFiles,contextFilesGenerated:x.contextFiles,tokensUsed:0,mode:C};await Ot(E,Y)}await m("push",100,"Done!"),console.log(),console.log(T.green("\u2713 Successfully pushed to storage")),console.log(),console.log(`Branch: ${T.cyan(g)}`),console.log(`Commit: ${T.cyan(l.slice(0,7))}`),console.log(`Mode: ${T.cyan(C)}`),console.log(`Context files: ${T.cyan(x.contextFiles)}`),n.mode==="sandbox"&&(console.log(),console.log(T.green("\u2713 Job completed")))}catch(E){t.fail("Upload failed"),E instanceof Error&&E.message.includes("R2 credentials")?(console.error(T.red(E.message)),console.log(),console.log("Required environment variables:"),console.log(" R2_ENDPOINT"),console.log(" R2_ACCESS_KEY_ID"),console.log(" R2_SECRET_ACCESS_KEY"),console.log(" R2_BUCKET")):console.error(T.red(`Error: ${E}`)),process.exit(1)}}catch(r){t.fail("Push failed"),console.error(T.red(`Error: ${r}`)),process.exit(1)}}import O from"chalk";import qt from"ora";async function _o(o,t){try{let e=await fetch(`${o}/api/auth/validate`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return e.ok?{valid:!0,userId:(await e.json()).userId}:e.status===401?{valid:!1,error:"Invalid or expired token"}:{valid:!1,error:`API error: ${e.status} ${e.statusText}`}}catch(e){return{valid:!1,error:`Connection failed: ${e}`}}}async function Wt(o){let t=qt(),e=o.apiUrl||Z;if(o.token){t.start("Validating token...");let n=await _o(e,o.token);n.valid||(t.fail("Authentication failed"),console.error(O.red(`Error: ${n.error}`)),process.exit(1));let i={apiToken:o.token,apiUrl:e,userId:n.userId};await st(i),t.succeed("Authenticated successfully"),console.log(),console.log(`Config saved to: ${O.cyan(M)}`);return}console.log(O.bold("ThinkerMD Login")),console.log(),console.log("To authenticate, you need an API token from ThinkerMD."),console.log(),console.log("Get your token at: "+O.cyan(`${e}/settings/tokens`)),console.log(),console.log("Then run:"),console.log(O.cyan(" tk login --token YOUR_TOKEN")),console.log();let r=await J();r.apiToken&&(console.log(O.green("You are already logged in.")),console.log(`User ID: ${O.cyan(r.userId||"unknown")}`),console.log(),console.log("To re-authenticate, run:"),console.log(O.cyan(" tk login --token NEW_TOKEN")))}async function Qt(){let o=qt();o.start("Logging out..."),await st({}),o.succeed("Logged out successfully"),console.log(),console.log(`Config cleared from: ${O.cyan(M)}`)}import{join as Zt}from"path";import u from"chalk";import Io from"ora";async function Ao(o){let{readFile:t}=await import("fs/promises"),{existsSync:e}=await import("fs"),r=Zt(P(o),"structure.json");try{if(e(r)){let n=await t(r,"utf-8");return JSON.parse(n)}}catch{}return null}async function No(o){let{readdir:t,stat:e}=await import("fs/promises"),r=0;async function n(i){let s;try{s=await t(i)}catch{return}for(let a of s){let d=Zt(i,a);try{(await e(d)).isDirectory()?await n(d):a==="CONTEXT.md"&&r++}catch{}}}return await n(o),r}async function to(){let o=Io(),t=process.cwd();try{await b(t)||(console.error(u.red("Error: Not a git repository")),process.exit(1));let e=await S(t),r=await $(),n=P(e);console.log(u.bold("ThinkerMD Status")),console.log();let i=await K(e),s=r.branch||await A(e),a=r.commitSha||await F(e);console.log(u.dim("Repository:")),console.log(` Name: ${u.cyan(i||"unknown")}`),console.log(` Branch: ${u.cyan(s)}`),console.log(` Commit: ${u.cyan(a.slice(0,7))}`),console.log();let{stat:d}=await import("fs/promises"),g=!1;try{await d(n),g=!0}catch{}if(!g){console.log(u.dim("Local Context:")),console.log(` Status: ${u.yellow("Not initialized")}`),console.log(),console.log("Run "+u.cyan("tk init")+" to create .thinker.md/");return}console.log(u.dim("Local Context:"));let l=await I(e),p=await Ao(e),h=await No(n);if(console.log(` Status: ${u.green("Initialized")}`),console.log(` Project ID: ${u.cyan(l?.projectId||"unknown")}`),console.log(` Context files: ${u.cyan(h)}`),p&&(console.log(` Directories: ${u.cyan(p.directoryCount)}`),console.log(` Files tracked: ${u.cyan(p.fileCount)}`)),l?.generatedAt){let c=new Date(l.generatedAt);console.log(` Generated: ${u.cyan(c.toLocaleString())}`)}l?.pulledFrom&&console.log(` Pulled from: ${u.cyan(l.pulledFrom.slice(0,7))}`),console.log();let w=r.metaRepoId||l?.projectId;if(w&&r.r2Endpoint){console.log(u.dim("Remote Storage:")),o.start("Checking remote...");try{let c=await rt(w,s);c?(o.stop(),console.log(` Latest commit: ${u.cyan(c.slice(0,7))}`),c===a?console.log(` Status: ${u.green("Up to date")}`):l?.pulledFrom===c?console.log(` Status: ${u.yellow("Local changes not pushed")}`):(console.log(` Status: ${u.yellow("Remote has newer version")}`),console.log(),console.log("Run "+u.cyan("tk pull")+" to download latest context"))):(o.stop(),console.log(` Status: ${u.yellow("No remote context found")}`),console.log(),console.log("Run "+u.cyan("tk push")+" to upload context"))}catch{o.stop(),console.log(` Status: ${u.dim("Unable to check (no credentials)")}`)}}console.log(),console.log(u.dim("Mode:")),console.log(` ${r.mode==="sandbox"?u.cyan("Sandbox"):u.cyan("Local")}`),r.mode==="sandbox"&&console.log(` Job ID: ${u.cyan(r.jobId||"unknown")}`)}catch(e){o.fail("Failed to get status"),console.error(u.red(`Error: ${e}`)),process.exit(1)}}import y from"chalk";import Oo from"ora";async function oo(o){let t=Oo(),e=process.cwd();try{await b(e)||(console.error(y.red("Error: Not a git repository")),process.exit(1));let r=await S(e);if((await $()).mode==="sandbox"){console.error(y.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 J();i.apiToken||(console.error(y.red("Error: Not logged in")),console.log(),console.log("Please authenticate first:"),console.log(y.cyan(" tk login --token YOUR_TOKEN")),process.exit(1));let s=i.apiUrl||Z,a=await K(r),d=o.branch||await A(r),g=await F(r);a||(console.error(y.red("Error: Unable to determine repository name")),console.log("Make sure this repository has a remote origin configured."),process.exit(1)),console.log(y.bold("Trigger Context Generation")),console.log(),console.log(`Repository: ${y.cyan(a)}`),console.log(`Branch: ${y.cyan(d)}`),console.log(`Commit: ${y.cyan(g.slice(0,7))}`),console.log(),t.start("Looking up meta-repository...");let l=await fetch(`${s}/api/meta-repositories/lookup?repo=${encodeURIComponent(a)}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});l.ok||(l.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(y.cyan(`${s}/dashboard`)),process.exit(1)),t.fail("API error"),console.error(y.red(`Error: ${l.status} ${l.statusText}`)),process.exit(1));let p=await l.json();t.succeed(`Found meta-repository: ${p.id}`),t.start("Triggering generation job...");let h=await fetch(`${s}/api/jobs/generate`,{method:"POST",headers:{Authorization:`Bearer ${i.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({metaRepositoryId:p.id,commitSha:g,branch:d,priority:"normal"})});if(!h.ok){t.fail("Failed to trigger generation");let c=await h.json().catch(()=>({}));console.error(y.red(`Error: ${c.error||h.statusText}`)),process.exit(1)}let w=await h.json();if(t.succeed("Generation job queued"),console.log(),console.log(`Job ID: ${y.cyan(w.jobId)}`),console.log(`Status: ${y.cyan(w.status)}`),w.estimatedPosition&&console.log(`Queue position: ${y.cyan(w.estimatedPosition)}`),console.log(),console.log("Track progress at:"),console.log(y.cyan(`${s}/dashboard/repositories/${p.id}/jobs/${w.jobId}`)),o.wait){console.log(),t.start("Waiting for completion...");let c=!1,x="";for(;!c;){await new Promise(C=>setTimeout(C,5e3));let L=await fetch(`${s}/api/jobs/${w.jobId}`,{headers:{Authorization:`Bearer ${i.apiToken}`}});if(L.ok){let C=await L.json();C.status!==x&&(x=C.status,t.text=`Status: ${C.status} (${C.progress||0}%)`),(C.status==="completed"||C.status==="failed")&&(c=!0,C.status==="completed"?(t.succeed("Generation completed!"),console.log(),console.log("Run "+y.cyan("tk pull")+" to download the generated context")):(t.fail("Generation failed"),C.errorMessage&&console.error(y.red(`Error: ${C.errorMessage}`))))}}}}catch(r){t.fail("Failed to trigger generation"),console.error(y.red(`Error: ${r}`)),process.exit(1)}}var R=new Fo;R.name("tk").description("ThinkerMD CLI - Manage .thinker.md/ context files").version("0.1.0");R.command("init").description("Create .thinker.md/ structure with empty CONTEXT.md files").option("-f, --force","Reinitialize even if .thinker.md/ exists").option("-m, --merge","Only add missing CONTEXT.md files, keep existing ones").action(o=>jt(o));R.command("validate").description("Verify .thinker.md/ structure matches repository").option("-q, --quiet","Only output errors, no summary").action(o=>Lt(o));R.command("pull").description("Download previous context from storage").option("-b, --branch <branch>","Specify branch to pull from").option("-c, --commit <sha>","Pull specific commit version").action(o=>Xt(o));R.command("push").description("Validate and upload .thinker.md/ to storage").option("--skip-validation","Skip validation before push").option("--force-full","Force full generation mode (ignore previous context)").action(o=>Yt(o));R.command("login").description("Authenticate with ThinkerMD API").option("-t, --token <token>","API token").option("--api-url <url>","Custom API URL").action(o=>Wt(o));R.command("logout").description("Remove saved authentication").action(()=>Qt());R.command("status").description("Show status of .thinker.md/ context").action(()=>to());R.command("generate").description("Trigger context generation via API").option("-b, --branch <branch>","Specify branch to generate for").option("-w, --wait","Wait for generation to complete").action(o=>oo(o));R.on("command:*",()=>{console.error(eo.red(`Unknown command: ${R.args.join(" ")}`)),console.log(),R.help()});async function ro(){try{await R.parseAsync(process.argv)}catch(o){o instanceof Error&&console.error(eo.red(`Error: ${o.message}`)),process.exit(1)}}ro().catch(o=>{console.error("Fatal error:",o),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkermd/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "ThinkerMD CLI for managing .thinker.md/ context files",
5
5
  "type": "module",
6
6
  "bin": {