toolnet-memory 0.2.9 → 0.2.11

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.
@@ -1,6 +1,6 @@
1
- import"dotenv/config";import{existsSync as he,readFileSync as ye}from"node:fs";import{homedir as Se}from"node:os";import{join as be}from"node:path";function xe(s){let e=s.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Ee(){let s=process.env.TOOLNET_GLOBAL_ENV??be(Se(),".config","toolnet-memory",".env");if(!he(s))return;let e=ye(s,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let o=r.indexOf("=");if(o<=0)continue;let n=r.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&process.env[n]===void 0&&(process.env[n]=xe(r.slice(o+1)))}}Ee();function M(s,e){return s===void 0?e:["1","true","yes","on"].includes(s.toLowerCase())}function R(s,e){if(!s)return e;let t=Number(s);return Number.isFinite(t)?t:e}function ee(){return{memory:{autoCapture:M(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:M(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:M(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:M(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:R(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:R(process.env.MEMORY_RERANK_TOP,10),finalContext:R(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:R(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:R(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as Ce}from"node:crypto";import{existsSync as J,mkdirSync as we,readFileSync as ve,renameSync as Pe,writeFileSync as ke}from"node:fs";import{basename as Te,dirname as I,join as F,parse as se,resolve as j}from"node:path";var oe=".toolnet",Re="project.json";function je(s){return Ce("sha256").update(s).digest("hex").slice(0,16)}function q(s){return F(s,oe,Re)}function Le(s){return J(q(s))}function Ne(s,e){let t=j(s),r=se(t).root;for(;;){if(Le(t))return t;if(t===r||e&&t===j(e))break;let o=I(t);if(o===t)break;t=o}return null}function Ae(s){let e=j(s),t=se(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(n=>J(F(e,n))))return e;if(e===t)break;let o=I(e);if(o===e)break;e=o}return j(s)}function Me(s){let e;try{e=JSON.parse(ve(s,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${s}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${s}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${s}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${s}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:I(I(s)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function te(s,e){let t=F(s,oe);we(t,{recursive:!0});let r=q(s),o=`${r}.tmp-${process.pid}`;ke(o,JSON.stringify(e,null,2)+`
3
- `,{encoding:"utf8",mode:384}),Pe(o,r)}function re(s,e){return{id:s.id,name:s.name,remote:s.remote,rootPath:e,createdAt:s.createdAt,updatedAt:s.updatedAt,graphVersion:s.graphVersion,memoryVersion:s.memoryVersion,metadata:s.metadata}}var O=class{detect(e=process.cwd()){let t=j(e),r=Ae(t),n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>J(F(r,d))),i=Ne(t,n?r:void 0);if(i){let d=q(i),m=Me(d);return m.rootPath!==i&&(m.rootPath=i,m.updatedAt=new Date().toISOString(),te(i,m)),re(m,i)}let c=new Date().toISOString(),l=Te(r),a={version:1,id:je(r),name:l,remote:l,rootPath:r,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0};return te(r,a),re(a,r)}};var Ie=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_\-]{20,}\b/g},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._\-]{16,}\b/gi},{type:"jwt",regex:/\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\b/g},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{type:"password_assignment",regex:/\b(password|passwd|pwd)\s*[:=]\s*["']?[^"'\s]{6,}["']?/gi},{type:"secret_assignment",regex:/\b(secret|api[_-]?key|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?[^"'\s]{8,}["']?/gi},{type:"cookie",regex:/\b(cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi}],_=class{scan(e){let t=[];for(let r of Ie){let o=new RegExp(r.regex.source,r.regex.flags);for(let n of e.matchAll(o))t.push({type:r.type,value:n[0]})}return t}hasSecrets(e){return this.scan(e).length>0}};var L=class{scanner=new _;sanitize(e){let t=e,r=this.scanner.scan(e),o=new Set;for(let n of r)o.add(n.type),t=t.split(n.value).join(`[REDACTED:${n.type}]`);return{text:t,redacted:r.length,secretTypes:[...o]}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[r,o]of Object.entries(e)){let n=r.toLowerCase();n.includes("password")||n.includes("secret")||n.includes("token")||n.includes("cookie")||n.includes("authorization")?t[r]="[REDACTED]":t[r]=this.sanitizeValue(o)}return t}return e}};import{homedir as Ye}from"node:os";import{join as Ze}from"node:path";import{DeleteObjectCommand as Oe,GetObjectCommand as Fe,HeadObjectCommand as _e,ListObjectsV2Command as $e,PutObjectCommand as Ge,S3Client as De}from"@aws-sdk/client-s3";import{getSignedUrl as ze}from"@aws-sdk/s3-request-presigner";var $=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new De({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Ge({Bucket:this.bucket,Key:e,Body:o,ContentType:r}))}async get(e){let t=await ze(this.client,new Fe({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new _e({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Oe({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let o=await this.client.send(new $e({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of o.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=o.IsTruncated?o.NextContinuationToken:void 0}while(r);return t}};import{access as ne,mkdir as Be,readFile as He,readdir as Ke,rm as We,stat as ie,writeFile as Ve}from"node:fs/promises";import{dirname as Ue,join as Je,relative as ae,resolve as qe}from"node:path";var N=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return qe(this.root,t)}async put(e,t){let r=this.path(e);await Be(Ue(r),{recursive:!0}),await Ve(r,t)}async get(e){try{return await He(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await ne(this.path(e)),!0}catch{return!1}}async delete(e){await We(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),r=[];try{await ne(t)}catch{return r}let o=async i=>{let c=await Ke(i,{withFileTypes:!0});for(let l of c){let a=Je(i,l.name);if(l.isDirectory()){await o(a);continue}let d=await ie(a);r.push({key:ae(this.root,a),size:d.size,updatedAt:d.mtime.toISOString()})}},n=await ie(t);return n.isDirectory()?await o(t):r.push({key:ae(this.root,t),size:n.size,updatedAt:n.mtime.toISOString()}),r}};function ce(s){let e=s.localRoot??Ze(Ye(),".toolnet-memory","storage");if(s.provider==="huggingface"){let t=s.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new $({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):(console.warn("[storage] Hugging Face credentials missing. Using local fallback."),new N(e))}return new N(e)}var A=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"graph","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};var G=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"code","chunks","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};var D=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"code","vectors","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};function le(s){let e=s.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function de(s){let e=s.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let o=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:e))}var z=class{constructor(e,t,r,o){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=r,this.folder=le(o??r),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),r=t,o=await this.provider.getText(e);if(o){let i;try{i=JSON.parse(o)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let n={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:t};await this.provider.put(e,JSON.stringify(n,null,2)+`
4
- `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=de(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,r){return await this.ensureRegistered(),this.provider.put(this.key(e),t,r)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import*as u from"typescript";var v=class{symbols=new Map;edges=new Map;addSymbol(e){this.symbols.set(e.id,e)}addEdge(e){this.edges.set(e.id,e)}getSymbol(e){return this.symbols.get(e)}findByName(e,t){return[...this.symbols.values()].filter(r=>r.projectId===e&&(r.name===t||r.qualifiedName===t))}allSymbols(e){return[...this.symbols.values()].filter(t=>t.projectId===e)}allEdges(e){return[...this.edges.values()].filter(t=>t.projectId===e)}import(e,t){for(let r of e)this.addSymbol(r);for(let r of t)this.addEdge(r)}clearProject(e){for(let[t,r]of this.symbols)r.projectId===e&&this.symbols.delete(t);for(let[t,r]of this.edges)r.projectId===e&&this.edges.delete(t)}};import{execFile as tt}from"node:child_process";import{promisify as rt}from"node:util";var fs=rt(tt);import{createHash as st}from"node:crypto";import{readFile as ot}from"node:fs/promises";import{join as nt}from"node:path";function pe(s){return st("sha256").update(s).digest("hex")}var B=class{sanitizer=new L;async build(e,t,r,o={}){let n=o.maxLines??80,i=o.overlapLines??10,c=new Map;for(let a of r){let d=c.get(a.filePath)??[];d.push(a),c.set(a.filePath,d)}let l=[];for(let[a,d]of c){let m;try{m=await ot(nt(t,a),"utf8")}catch{continue}let g=m.split(/\r?\n/),S=d.filter(h=>h.type!=="file"&&typeof h.startLine=="number"&&typeof h.endLine=="number");if(S.length>0)for(let h of S)l.push(...this.chunkRange({projectId:e,filePath:a,lines:g,symbol:h,startLine:h.startLine,endLine:h.endLine,maxLines:n,overlapLines:i}));else l.push(...this.chunkRange({projectId:e,filePath:a,lines:g,startLine:1,endLine:Math.max(1,g.length),maxLines:n,overlapLines:i}))}return l}chunkRange(e){let{projectId:t,filePath:r,lines:o,symbol:n,maxLines:i}=e,c=Math.min(e.overlapLines,Math.max(0,i-1)),l=Math.max(1,e.startLine),a=Math.min(o.length,Math.max(l,e.endLine)),d=[],m=l,g=0;for(;m<=a;){let S=Math.min(a,m+i-1),h=o.slice(m-1,S).join(`
5
- `).trim();if(h){let x=this.sanitizer.sanitize(h).text,P=pe(x),k=[t,r,n?.id??"file",String(g),P].join(":");d.push({id:pe(k).slice(0,32),projectId:t,filePath:r,symbolId:n?.id,symbolName:n?.qualifiedName??n?.name,symbolType:n?.type,startLine:m,endLine:S,content:x,contentHash:P})}if(S>=a)break;m=S-c+1,g++}return d}};var H=class{records=new Map;upsert(e){this.records.set(e.id,e)}get(e){return this.records.get(e)}has(e){return this.records.has(e)}remove(e){return this.records.delete(e)}clearProject(e){let t=0;for(let[r,o]of this.records)o.projectId===e&&(this.records.delete(r),t++);return t}exportProject(e){return[...this.records.values()].filter(t=>t.projectId===e)}importRecords(e){let t=0;for(let r of e)!r?.id||!r?.projectId||!Array.isArray(r.vector)||(this.records.set(r.id,r),t++);return t}search(e,t,r=10){let o=[];for(let n of this.records.values()){if(n.projectId!==e)continue;let i=it(t,n.vector);o.push({id:n.id,score:i,metadata:n.metadata})}return o.sort((n,i)=>i.score-n.score).slice(0,r)}size(){return this.records.size}};function it(s,e){if(s.length===0||e.length===0||s.length!==e.length)return 0;let t=0,r=0,o=0;for(let n=0;n<s.length;n++){let i=s[n]??0,c=e[n]??0;t+=i*c,r+=i*i,o+=c*c}return r===0||o===0?0:t/(Math.sqrt(r)*Math.sqrt(o))}function Y(s){return s.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}_\-./]+/gu," ").split(/\s+/).map(e=>e.trim()).filter(e=>e.length>1)}function me(s,e){let t=s.toLowerCase().replaceAll("\\","/"),r=e.toLowerCase(),o=/\b(test|tests|spec|fixture|mock)\b/.test(r);return(t.startsWith("tests/")||t.includes("/tests/")||t.includes("__tests__")||/\.(test|spec)\.[^.]+$/.test(t))&&!o?.35:t.includes("/generated/")||t.includes(".generated.")?.25:1}var K=class{constructor(e){this.options=e}options;vectors=new H;chunks=new Map;async initialize(){let{projectId:e,rootPath:t,storage:r,embeddings:o,graph:n,model:i}=this.options,c=new G(r),l=new D(r),a=await new B().build(e,t,n.allSymbols(e));this.chunks.clear();for(let f of a)this.chunks.set(f.id,f);let d=await c.load(e),m=!d||d.chunks.length!==a.length||d.chunks.some(f=>!this.chunks.has(f.id));m&&await c.save({version:1,projectId:e,updatedAt:new Date().toISOString(),chunks:a});let g=await l.load(e),S=0;g&&g.model===i&&(S=this.vectors.importRecords(g.records));let h=new Set(a.map(f=>f.id)),x=0;for(let f of this.vectors.exportProject(e))h.has(f.id)||this.vectors.remove(f.id)&&x++;let P=a.filter(f=>{let C=this.vectors.get(f.id);return C?C.metadata?.contentHash!==f.contentHash:!0}),k=0,Q=8,T=this.vectors.exportProject(e)[0]?.vector.length;for(let f=0;f<P.length;f+=Q){let C=P.slice(f,f+Q),ue=C.map(b=>[`file: ${b.filePath}`,b.symbolName?`symbol: ${b.symbolName}`:"",b.symbolType?`type: ${b.symbolType}`:"",b.content].filter(Boolean).join(`
6
- `)),ge=await o.embedMany(ue);for(let b=0;b<C.length;b++){let E=C[b],w=ge[b];if(!(!E||!w||w.length===0)){if(T===void 0&&(T=w.length),w.length!==T){console.warn(`[code-semantic] skip dimension mismatch: ${w.length} != ${T}`);continue}this.vectors.upsert({id:E.id,projectId:e,vector:w,metadata:{contentHash:E.contentHash,filePath:E.filePath,symbolName:E.symbolName,symbolType:E.symbolType,startLine:E.startLine,endLine:E.endLine}}),k++}}}let U=this.vectors.exportProject(e),X=k>0||x>0||!g||g.model!==i;return X&&await l.save({version:1,projectId:e,model:i,dimensions:U[0]?.vector.length??T??0,updatedAt:new Date().toISOString(),records:U}),{chunks:a.length,vectorsLoaded:S,vectorsIndexed:k,vectorsRemoved:x,totalVectors:U.length,savedChunks:m,savedVectors:X}}async search(e,t=8){let r=await this.options.embeddings.embed(e),o=this.vectors.search(this.options.projectId,r,Math.max(t*4,20)),n=new Set(Y(e)),i=[];for(let c of o){let l=this.chunks.get(c.id);if(!l)continue;let a=new Set(Y([l.filePath,l.symbolName??"",l.symbolType??"",l.content].join(" "))),d=0;for(let x of n)a.has(x)&&d++;let m=n.size===0?0:d/n.size,g=Math.max(0,c.score),S=me(l.filePath,e),h=(g*.75+m*.25)*S;i.push({chunk:l,score:h,vectorScore:g,lexicalScore:m})}return i.sort((c,l)=>l.score-c.score).slice(0,t)}size(){return this.chunks.size}};import*as y from"typescript";import*as p from"typescript";var xo=new Set([p.SyntaxKind.EqualsToken,p.SyntaxKind.PlusEqualsToken,p.SyntaxKind.MinusEqualsToken,p.SyntaxKind.AsteriskEqualsToken,p.SyntaxKind.SlashEqualsToken,p.SyntaxKind.PercentEqualsToken,p.SyntaxKind.QuestionQuestionEqualsToken,p.SyntaxKind.AmpersandAmpersandEqualsToken,p.SyntaxKind.BarBarEqualsToken]);var W=class{constructor(e){this.options=e}options;name="huggingface";dimensions=384;async embed(e){let[t]=await this.embedMany([e]);return t}async embedMany(e){if(e.length===0)return[];let t=`https://router.huggingface.co/hf-inference/models/${this.options.model}/pipeline/feature-extraction`,r=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${this.options.token}`,"Content-Type":"application/json"},body:JSON.stringify({inputs:e,options:{wait_for_model:!0}})});if(!r.ok)throw new Error(`HF embedding failed: ${r.status} ${await r.text()}`);let o=await r.json();if(!Array.isArray(o))throw new Error("HF embedding returned invalid response");return o.map(n=>ct(n))}};function ct(s){if(!Array.isArray(s))throw new Error("Invalid embedding");if(s.length>0&&typeof s[0]=="number")return s;if(s.length>0&&Array.isArray(s[0])){let e=s;if(e.length===0)return[];let t=e[0].length,r=new Array(t).fill(0);for(let o of e)for(let n=0;n<t;n++)r[n]+=o[n]??0;for(let o=0;o<t;o++)r[o]/=e.length;return r}throw new Error("Unsupported embedding shape")}import{createHash as lt}from"node:crypto";var V=class{name="hash-fallback";dimensions=128;async embed(e){let t=new Array(this.dimensions).fill(0),r=e.toLowerCase().split(/\s+/).filter(Boolean);for(let o of r){let i=lt("sha256").update(o).digest().readUInt32BE(0)%this.dimensions;t[i]+=1}return dt(t)}async embedMany(e){return Promise.all(e.map(t=>this.embed(t)))}};function dt(s){let e=Math.sqrt(s.reduce((t,r)=>t+r*r,0));return e===0?s:s.map(t=>t/e)}var Z=class{constructor(e,t=new V){this.primary=e;this.fallback=t}primary;fallback;name="resilient";async embed(e){if(this.primary)try{return await this.primary.embed(e)}catch(t){console.warn("[embedding] primary failed, fallback enabled:",t instanceof Error?t.message:t)}return this.fallback.embed(e)}async embedMany(e){if(this.primary)try{return await this.primary.embedMany(e)}catch(t){console.warn("[embedding] primary failed, fallback enabled:",t instanceof Error?t.message:t)}return this.fallback.embedMany(e)}};function fe(){let s=process.env.HF_TOKEN,e=process.env.HF_EMBEDDING_MODEL??"sentence-transformers/all-MiniLM-L6-v2",t=s?new W({token:s,model:e}):null;return new Z(t)}async function pt(){let s=ee(),e=new O().detect(),t=ce({provider:s.storage.provider,huggingface:s.storage.huggingface,localRoot:s.storage.localRoot}),r=new z(t,e.id,e.name,e.remote??e.name),o=new v,n=await new A(r).load(e.id);if(!n)throw new Error("Code graph missing. Run npm run code:index:test first.");o.import(n.symbols,n.edges);let i=new K({projectId:e.id,rootPath:e.rootPath,model:process.env.HF_EMBEDDING_MODEL??"sentence-transformers/all-MiniLM-L6-v2",storage:r,embeddings:fe(),graph:o}),c=await i.initialize(),l=process.argv.slice(2).join(" ")||"memory storage hugging face",a=await i.search(l,5);console.log({ok:!0,query:l,...c,results:a.map(d=>({score:Number(d.score.toFixed(4)),file:d.chunk.filePath,symbol:d.chunk.symbolName,lines:`${d.chunk.startLine}-${d.chunk.endLine}`,preview:d.chunk.content.replace(/\s+/g," ").slice(0,140)}))})}pt().catch(s=>{console.error(s),process.exit(1)});
1
+ import"dotenv/config";import{existsSync as Se,readFileSync as be}from"node:fs";import{homedir as xe}from"node:os";import{join as Ee}from"node:path";function Ce(s){let e=s.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function we(){let s=process.env.TOOLNET_GLOBAL_ENV??Ee(xe(),".config","toolnet-memory",".env");if(!Se(s))return;let e=be(s,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let o=r.indexOf("=");if(o<=0)continue;let n=r.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&process.env[n]===void 0&&(process.env[n]=Ce(r.slice(o+1)))}}we();function R(s,e){return s===void 0?e:["1","true","yes","on"].includes(s.toLowerCase())}function j(s,e){if(!s)return e;let t=Number(s);return Number.isFinite(t)?t:e}function re(){return{memory:{autoCapture:R(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:R(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:R(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:R(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:j(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:j(process.env.MEMORY_RERANK_TOP,10),finalContext:j(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:j(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:R(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:j(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as ve}from"node:crypto";import{existsSync as q,mkdirSync as Pe,readFileSync as ke,renameSync as Te,writeFileSync as Re}from"node:fs";import{basename as je,dirname as O,join as F,parse as ne,resolve as A}from"node:path";var ie=".toolnet",Ae="project.json";function Le(s){return ve("sha256").update(s).digest("hex").slice(0,16)}function J(s){return F(s,ie,Ae)}function Ne(s){return q(J(s))}function Me(s,e){let t=A(s),r=ne(t).root;for(;;){if(Ne(t))return t;if(t===r||e&&t===A(e))break;let o=O(t);if(o===t)break;t=o}return null}function Ie(s){let e=A(s),t=ne(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(n=>q(F(e,n))))return e;if(e===t)break;let o=O(e);if(o===e)break;e=o}return A(s)}function Oe(s){let e;try{e=JSON.parse(ke(s,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${s}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${s}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${s}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${s}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:O(O(s)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function se(s,e){let t=F(s,ie);Pe(t,{recursive:!0});let r=J(s),o=`${r}.tmp-${process.pid}`;Re(o,JSON.stringify(e,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),Te(o,r)}function oe(s,e){return{id:s.id,name:s.name,remote:s.remote,rootPath:e,createdAt:s.createdAt,updatedAt:s.updatedAt,graphVersion:s.graphVersion,memoryVersion:s.memoryVersion,metadata:s.metadata}}var _=class{detect(e=process.cwd()){let t=A(e),r=Ie(t),n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>q(F(r,d))),i=Me(t,n?r:void 0);if(i){let d=J(i),m=Oe(d);return m.rootPath!==i&&(m.rootPath=i,m.updatedAt=new Date().toISOString(),se(i,m)),oe(m,i)}let c=new Date().toISOString(),l=je(r),a={version:1,id:Le(r),name:l,remote:l,rootPath:r,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0};return se(r,a),oe(a,r)}};var _e=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_\-]{20,}\b/g},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._\-]{16,}\b/gi},{type:"jwt",regex:/\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\b/g},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{type:"password_assignment",regex:/\b(password|passwd|pwd)\s*[:=]\s*["']?[^"'\s]{6,}["']?/gi},{type:"secret_assignment",regex:/\b(secret|api[_-]?key|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?[^"'\s]{8,}["']?/gi},{type:"cookie",regex:/\b(cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi}],$=class{scan(e){let t=[];for(let r of _e){let o=new RegExp(r.regex.source,r.regex.flags);for(let n of e.matchAll(o))t.push({type:r.type,value:n[0]})}return t}hasSecrets(e){return this.scan(e).length>0}};var L=class{scanner=new $;sanitize(e){let t=e,r=this.scanner.scan(e),o=new Set;for(let n of r)o.add(n.type),t=t.split(n.value).join(`[REDACTED:${n.type}]`);return{text:t,redacted:r.length,secretTypes:[...o]}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[r,o]of Object.entries(e)){let n=r.toLowerCase();n.includes("password")||n.includes("secret")||n.includes("token")||n.includes("cookie")||n.includes("authorization")?t[r]="[REDACTED]":t[r]=this.sanitizeValue(o)}return t}return e}};import{homedir as nt}from"node:os";import{join as it}from"node:path";import{DeleteObjectCommand as Fe,GetObjectCommand as $e,HeadObjectCommand as De,ListObjectsV2Command as Ge,PutObjectCommand as Ke,S3Client as ze}from"@aws-sdk/client-s3";import{getSignedUrl as Be}from"@aws-sdk/s3-request-presigner";var D=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new ze({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Ke({Bucket:this.bucket,Key:e,Body:o,ContentType:r}))}async get(e){let t=await Be(this.client,new $e({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new De({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Fe({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let o=await this.client.send(new Ge({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of o.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=o.IsTruncated?o.NextContinuationToken:void 0}while(r);return t}};import{access as ae,mkdir as He,readFile as We,readdir as Ue,rm as Ve,stat as ce,writeFile as Ye}from"node:fs/promises";import{dirname as qe,join as Je,relative as le,resolve as Qe}from"node:path";var N=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return Qe(this.root,t)}async put(e,t){let r=this.path(e);await He(qe(r),{recursive:!0}),await Ye(r,t)}async get(e){try{return await We(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await ae(this.path(e)),!0}catch{return!1}}async delete(e){await Ve(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),r=[];try{await ae(t)}catch{return r}let o=async i=>{let c=await Ue(i,{withFileTypes:!0});for(let l of c){let a=Je(i,l.name);if(l.isDirectory()){await o(a);continue}let d=await ce(a);r.push({key:le(this.root,a),size:d.size,updatedAt:d.mtime.toISOString()})}},n=await ce(t);return n.isDirectory()?await o(t):r.push({key:le(this.root,t),size:n.size,updatedAt:n.mtime.toISOString()}),r}};import{DeleteObjectCommand as Ze,GetObjectCommand as Xe,HeadObjectCommand as et,ListObjectsV2Command as tt,PutObjectCommand as rt,S3Client as st}from"@aws-sdk/client-s3";import{getSignedUrl as ot}from"@aws-sdk/s3-request-presigner";var M=class{name;client;bucket;constructor(e){this.name=e.name??"s3",this.bucket=e.bucket,this.client=new st({region:e.region??"us-east-1",endpoint:e.endpoint||void 0,forcePathStyle:e.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new rt({Bucket:this.bucket,Key:e,Body:o,ContentType:r}))}async get(e){let t=await ot(this.client,new Xe({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`${this.name} download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new et({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Ze({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let o=await this.client.send(new tt({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of o.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=o.IsTruncated?o.NextContinuationToken:void 0}while(r);return t}};function Q(s,e){return console.warn(e),new N(s)}function de(s){let e=s.localRoot??it(nt(),".toolnet-memory","storage");if(s.provider==="r2"){let t=s.r2;return t?.accountId&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new M({name:"r2",endpoint:`https://${t.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:t.bucket,forcePathStyle:!0,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):Q(e,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(s.provider==="s3"){let t=s.s3;return t?.bucket&&t.accessKeyId&&t.secretAccessKey?new M({name:"s3",endpoint:t.endpoint,region:t.region??"us-east-1",bucket:t.bucket,forcePathStyle:t.forcePathStyle??!1,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):Q(e,"[storage] S3 credentials missing. Using local fallback.")}if(s.provider==="huggingface"){let t=s.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new D({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):Q(e,"[storage] Hugging Face credentials missing. Using local fallback.")}return new N(e)}var I=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"graph","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};var G=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"code","chunks","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};var K=class{constructor(e){this.storage=e}storage;key(e){return["projects",e,"code","vectors","current.json"].join("/")}async load(e){let t=await this.storage.getText(this.key(e));return t?JSON.parse(t):null}async save(e){await this.storage.put(this.key(e.projectId),JSON.stringify(e),"application/json")}};function pe(s){let e=s.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function me(s){let e=s.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let o=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:e))}var z=class{constructor(e,t,r,o){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=r,this.folder=pe(o??r),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),r=t,o=await this.provider.getText(e);if(o){let i;try{i=JSON.parse(o)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let n={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:t};await this.provider.put(e,JSON.stringify(n,null,2)+`
4
+ `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=me(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,r){return await this.ensureRegistered(),this.provider.put(this.key(e),t,r)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import*as u from"typescript";var v=class{symbols=new Map;edges=new Map;addSymbol(e){this.symbols.set(e.id,e)}addEdge(e){this.edges.set(e.id,e)}getSymbol(e){return this.symbols.get(e)}findByName(e,t){return[...this.symbols.values()].filter(r=>r.projectId===e&&(r.name===t||r.qualifiedName===t))}allSymbols(e){return[...this.symbols.values()].filter(t=>t.projectId===e)}allEdges(e){return[...this.edges.values()].filter(t=>t.projectId===e)}import(e,t){for(let r of e)this.addSymbol(r);for(let r of t)this.addEdge(r)}clearProject(e){for(let[t,r]of this.symbols)r.projectId===e&&this.symbols.delete(t);for(let[t,r]of this.edges)r.projectId===e&&this.edges.delete(t)}};import{execFile as dt}from"node:child_process";import{promisify as pt}from"node:util";var js=pt(dt);import{createHash as mt}from"node:crypto";import{readFile as ft}from"node:fs/promises";import{join as ut}from"node:path";function fe(s){return mt("sha256").update(s).digest("hex")}var B=class{sanitizer=new L;async build(e,t,r,o={}){let n=o.maxLines??80,i=o.overlapLines??10,c=new Map;for(let a of r){let d=c.get(a.filePath)??[];d.push(a),c.set(a.filePath,d)}let l=[];for(let[a,d]of c){let m;try{m=await ft(ut(t,a),"utf8")}catch{continue}let g=m.split(/\r?\n/),S=d.filter(h=>h.type!=="file"&&typeof h.startLine=="number"&&typeof h.endLine=="number");if(S.length>0)for(let h of S)l.push(...this.chunkRange({projectId:e,filePath:a,lines:g,symbol:h,startLine:h.startLine,endLine:h.endLine,maxLines:n,overlapLines:i}));else l.push(...this.chunkRange({projectId:e,filePath:a,lines:g,startLine:1,endLine:Math.max(1,g.length),maxLines:n,overlapLines:i}))}return l}chunkRange(e){let{projectId:t,filePath:r,lines:o,symbol:n,maxLines:i}=e,c=Math.min(e.overlapLines,Math.max(0,i-1)),l=Math.max(1,e.startLine),a=Math.min(o.length,Math.max(l,e.endLine)),d=[],m=l,g=0;for(;m<=a;){let S=Math.min(a,m+i-1),h=o.slice(m-1,S).join(`
5
+ `).trim();if(h){let x=this.sanitizer.sanitize(h).text,P=fe(x),k=[t,r,n?.id??"file",String(g),P].join(":");d.push({id:fe(k).slice(0,32),projectId:t,filePath:r,symbolId:n?.id,symbolName:n?.qualifiedName??n?.name,symbolType:n?.type,startLine:m,endLine:S,content:x,contentHash:P})}if(S>=a)break;m=S-c+1,g++}return d}};var H=class{records=new Map;upsert(e){this.records.set(e.id,e)}get(e){return this.records.get(e)}has(e){return this.records.has(e)}remove(e){return this.records.delete(e)}clearProject(e){let t=0;for(let[r,o]of this.records)o.projectId===e&&(this.records.delete(r),t++);return t}exportProject(e){return[...this.records.values()].filter(t=>t.projectId===e)}importRecords(e){let t=0;for(let r of e)!r?.id||!r?.projectId||!Array.isArray(r.vector)||(this.records.set(r.id,r),t++);return t}search(e,t,r=10){let o=[];for(let n of this.records.values()){if(n.projectId!==e)continue;let i=gt(t,n.vector);o.push({id:n.id,score:i,metadata:n.metadata})}return o.sort((n,i)=>i.score-n.score).slice(0,r)}size(){return this.records.size}};function gt(s,e){if(s.length===0||e.length===0||s.length!==e.length)return 0;let t=0,r=0,o=0;for(let n=0;n<s.length;n++){let i=s[n]??0,c=e[n]??0;t+=i*c,r+=i*i,o+=c*c}return r===0||o===0?0:t/(Math.sqrt(r)*Math.sqrt(o))}function Z(s){return s.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}_\-./]+/gu," ").split(/\s+/).map(e=>e.trim()).filter(e=>e.length>1)}function ue(s,e){let t=s.toLowerCase().replaceAll("\\","/"),r=e.toLowerCase(),o=/\b(test|tests|spec|fixture|mock)\b/.test(r);return(t.startsWith("tests/")||t.includes("/tests/")||t.includes("__tests__")||/\.(test|spec)\.[^.]+$/.test(t))&&!o?.35:t.includes("/generated/")||t.includes(".generated.")?.25:1}var W=class{constructor(e){this.options=e}options;vectors=new H;chunks=new Map;async initialize(){let{projectId:e,rootPath:t,storage:r,embeddings:o,graph:n,model:i}=this.options,c=new G(r),l=new K(r),a=await new B().build(e,t,n.allSymbols(e));this.chunks.clear();for(let f of a)this.chunks.set(f.id,f);let d=await c.load(e),m=!d||d.chunks.length!==a.length||d.chunks.some(f=>!this.chunks.has(f.id));m&&await c.save({version:1,projectId:e,updatedAt:new Date().toISOString(),chunks:a});let g=await l.load(e),S=0;g&&g.model===i&&(S=this.vectors.importRecords(g.records));let h=new Set(a.map(f=>f.id)),x=0;for(let f of this.vectors.exportProject(e))h.has(f.id)||this.vectors.remove(f.id)&&x++;let P=a.filter(f=>{let C=this.vectors.get(f.id);return C?C.metadata?.contentHash!==f.contentHash:!0}),k=0,ee=8,T=this.vectors.exportProject(e)[0]?.vector.length;for(let f=0;f<P.length;f+=ee){let C=P.slice(f,f+ee),he=C.map(b=>[`file: ${b.filePath}`,b.symbolName?`symbol: ${b.symbolName}`:"",b.symbolType?`type: ${b.symbolType}`:"",b.content].filter(Boolean).join(`
6
+ `)),ye=await o.embedMany(he);for(let b=0;b<C.length;b++){let E=C[b],w=ye[b];if(!(!E||!w||w.length===0)){if(T===void 0&&(T=w.length),w.length!==T){console.warn(`[code-semantic] skip dimension mismatch: ${w.length} != ${T}`);continue}this.vectors.upsert({id:E.id,projectId:e,vector:w,metadata:{contentHash:E.contentHash,filePath:E.filePath,symbolName:E.symbolName,symbolType:E.symbolType,startLine:E.startLine,endLine:E.endLine}}),k++}}}let Y=this.vectors.exportProject(e),te=k>0||x>0||!g||g.model!==i;return te&&await l.save({version:1,projectId:e,model:i,dimensions:Y[0]?.vector.length??T??0,updatedAt:new Date().toISOString(),records:Y}),{chunks:a.length,vectorsLoaded:S,vectorsIndexed:k,vectorsRemoved:x,totalVectors:Y.length,savedChunks:m,savedVectors:te}}async search(e,t=8){let r=await this.options.embeddings.embed(e),o=this.vectors.search(this.options.projectId,r,Math.max(t*4,20)),n=new Set(Z(e)),i=[];for(let c of o){let l=this.chunks.get(c.id);if(!l)continue;let a=new Set(Z([l.filePath,l.symbolName??"",l.symbolType??"",l.content].join(" "))),d=0;for(let x of n)a.has(x)&&d++;let m=n.size===0?0:d/n.size,g=Math.max(0,c.score),S=ue(l.filePath,e),h=(g*.75+m*.25)*S;i.push({chunk:l,score:h,vectorScore:g,lexicalScore:m})}return i.sort((c,l)=>l.score-c.score).slice(0,t)}size(){return this.chunks.size}};import*as y from"typescript";import*as p from"typescript";var _o=new Set([p.SyntaxKind.EqualsToken,p.SyntaxKind.PlusEqualsToken,p.SyntaxKind.MinusEqualsToken,p.SyntaxKind.AsteriskEqualsToken,p.SyntaxKind.SlashEqualsToken,p.SyntaxKind.PercentEqualsToken,p.SyntaxKind.QuestionQuestionEqualsToken,p.SyntaxKind.AmpersandAmpersandEqualsToken,p.SyntaxKind.BarBarEqualsToken]);var U=class{constructor(e){this.options=e}options;name="huggingface";dimensions=384;async embed(e){let[t]=await this.embedMany([e]);return t}async embedMany(e){if(e.length===0)return[];let t=`https://router.huggingface.co/hf-inference/models/${this.options.model}/pipeline/feature-extraction`,r=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${this.options.token}`,"Content-Type":"application/json"},body:JSON.stringify({inputs:e,options:{wait_for_model:!0}})});if(!r.ok)throw new Error(`HF embedding failed: ${r.status} ${await r.text()}`);let o=await r.json();if(!Array.isArray(o))throw new Error("HF embedding returned invalid response");return o.map(n=>yt(n))}};function yt(s){if(!Array.isArray(s))throw new Error("Invalid embedding");if(s.length>0&&typeof s[0]=="number")return s;if(s.length>0&&Array.isArray(s[0])){let e=s;if(e.length===0)return[];let t=e[0].length,r=new Array(t).fill(0);for(let o of e)for(let n=0;n<t;n++)r[n]+=o[n]??0;for(let o=0;o<t;o++)r[o]/=e.length;return r}throw new Error("Unsupported embedding shape")}import{createHash as St}from"node:crypto";var V=class{name="hash-fallback";dimensions=128;async embed(e){let t=new Array(this.dimensions).fill(0),r=e.toLowerCase().split(/\s+/).filter(Boolean);for(let o of r){let i=St("sha256").update(o).digest().readUInt32BE(0)%this.dimensions;t[i]+=1}return bt(t)}async embedMany(e){return Promise.all(e.map(t=>this.embed(t)))}};function bt(s){let e=Math.sqrt(s.reduce((t,r)=>t+r*r,0));return e===0?s:s.map(t=>t/e)}var X=class{constructor(e,t=new V){this.primary=e;this.fallback=t}primary;fallback;name="resilient";async embed(e){if(this.primary)try{return await this.primary.embed(e)}catch(t){console.warn("[embedding] primary failed, fallback enabled:",t instanceof Error?t.message:t)}return this.fallback.embed(e)}async embedMany(e){if(this.primary)try{return await this.primary.embedMany(e)}catch(t){console.warn("[embedding] primary failed, fallback enabled:",t instanceof Error?t.message:t)}return this.fallback.embedMany(e)}};function ge(){let s=process.env.HF_TOKEN,e=process.env.HF_EMBEDDING_MODEL??"sentence-transformers/all-MiniLM-L6-v2",t=s?new U({token:s,model:e}):null;return new X(t)}async function xt(){let s=re(),e=new _().detect(),t=de({provider:s.storage.provider,huggingface:s.storage.huggingface,localRoot:s.storage.localRoot}),r=new z(t,e.id,e.name,e.remote??e.name),o=new v,n=await new I(r).load(e.id);if(!n)throw new Error("Code graph missing. Run npm run code:index:test first.");o.import(n.symbols,n.edges);let i=new W({projectId:e.id,rootPath:e.rootPath,model:process.env.HF_EMBEDDING_MODEL??"sentence-transformers/all-MiniLM-L6-v2",storage:r,embeddings:ge(),graph:o}),c=await i.initialize(),l=process.argv.slice(2).join(" ")||"memory storage hugging face",a=await i.search(l,5);console.log({ok:!0,query:l,...c,results:a.map(d=>({score:Number(d.score.toFixed(4)),file:d.chunk.filePath,symbol:d.chunk.symbolName,lines:`${d.chunk.startLine}-${d.chunk.endLine}`,preview:d.chunk.content.replace(/\s+/g," ").slice(0,140)}))})}xt().catch(s=>{console.error(s),process.exit(1)});
package/bundle/setup.js CHANGED
@@ -1,5 +1,5 @@
1
- import f from"node:fs";import Ee from"node:os";import K from"node:path";import Se from"node:readline/promises";import{stdin as g,stdout as y}from"node:process";import{existsSync as h}from"node:fs";import{homedir as C}from"node:os";import{join as x}from"node:path";import{spawnSync as ge}from"node:child_process";import{existsSync as U,mkdirSync as L,readFileSync as J,writeFileSync as q}from"node:fs";import{homedir as G}from"node:os";import{dirname as X,join as V}from"node:path";function W(t){return`'${t.replace(/'/g,"'\\''")}'`}function F(t={}){let e=t.hooksFile??V(G(),".gemini","config","hooks.json");L(X(e),{recursive:!0});let o={};if(U(e))try{o=JSON.parse(J(e,"utf8"))}catch(a){throw new Error(`Invalid existing Agy hooks.json: ${a instanceof Error?a.message:String(a)}`)}let r=t.binary??"toolnet-memory",n=`${W(r)} session:agy-hook`;return o["toolnet-memory"]={enabled:!0,PreInvocation:[{type:"command",command:`${n} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${n} post`,timeout:15}],Stop:[{type:"command",command:`${n} stop`,timeout:30}]},q(e,JSON.stringify(o,null,2)+`
2
- `,{encoding:"utf8",mode:384}),e}import{existsSync as Z,mkdirSync as k,readFileSync as Q,writeFileSync as v}from"node:fs";import{homedir as _}from"node:os";import{join as E}from"node:path";var I="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",S="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function z(){let t=E(_(),".config","opencode","AGENTS.md");k(E(_(),".config","opencode"),{recursive:!0});let e=`${I}
1
+ import m from"node:fs";import Te from"node:os";import q from"node:path";import Ae from"node:readline/promises";import{stdin as d,stdout as S}from"node:process";import{existsSync as A}from"node:fs";import{homedir as R}from"node:os";import{join as N}from"node:path";import{spawnSync as me}from"node:child_process";import{existsSync as V,mkdirSync as W,readFileSync as X,writeFileSync as Z}from"node:fs";import{homedir as Q}from"node:os";import{dirname as z,join as ee}from"node:path";function te(e){return`'${e.replace(/'/g,"'\\''")}'`}function Y(e={}){let t=e.hooksFile??ee(Q(),".gemini","config","hooks.json");W(z(t),{recursive:!0});let o={};if(V(t))try{o=JSON.parse(X(t,"utf8"))}catch(s){throw new Error(`Invalid existing Agy hooks.json: ${s instanceof Error?s.message:String(s)}`)}let r=e.binary??"toolnet-memory",n=`${te(r)} session:agy-hook`;return o["toolnet-memory"]={enabled:!0,PreInvocation:[{type:"command",command:`${n} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${n} post`,timeout:15}],Stop:[{type:"command",command:`${n} stop`,timeout:30}]},Z(t,JSON.stringify(o,null,2)+`
2
+ `,{encoding:"utf8",mode:384}),t}import{existsSync as oe,mkdirSync as K,readFileSync as ne,writeFileSync as D}from"node:fs";import{homedir as O}from"node:os";import{join as p}from"node:path";var $="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",C="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function re(){let e=p(O(),".config","opencode","AGENTS.md");K(p(O(),".config","opencode"),{recursive:!0});let t=`${$}
3
3
  ## ToolNet Memory \u2014 Project Continuity
4
4
 
5
5
  When the current workspace belongs to a ToolNet project, identified by a nearest ancestor containing \`.toolnet/project.json\`:
@@ -12,10 +12,10 @@ When the current workspace belongs to a ToolNet project, identified by a nearest
12
12
  6. Never invent missing rationale. If ToolNet says a reason was not explicitly recorded, inspect the implementation or ask rather than guessing.
13
13
 
14
14
  This applies automatically to every ToolNet-managed project.
15
- ${S}`,o=Z(t)?Q(t,"utf8"):"",r=o.indexOf(I),n=o.indexOf(S);return r>=0&&n>=r?o=o.slice(0,r)+e+o.slice(n+S.length):(o=o.trimEnd(),o&&(o+=`
15
+ ${C}`,o=oe(e)?ne(e,"utf8"):"",r=o.indexOf($),n=o.indexOf(C);return r>=0&&n>=r?o=o.slice(0,r)+t+o.slice(n+C.length):(o=o.trimEnd(),o&&(o+=`
16
16
 
17
- `),o+=e),v(t,o.trimEnd()+`
18
- `,{encoding:"utf8",mode:384}),t}function w(t={}){let e=t.directory??E(_(),".config","opencode","plugins");k(e,{recursive:!0}),z();let o=E(e,"toolnet-memory.js"),r=t.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`// Generated by ToolNet Memory.
17
+ `),o+=t),D(e,o.trimEnd()+`
18
+ `,{encoding:"utf8",mode:384}),e}function w(e={}){let t=e.directory??p(O(),".config","opencode","plugins");K(t,{recursive:!0}),re();let o=p(t,"toolnet-memory.js"),r=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`// Generated by ToolNet Memory.
19
19
  // OpenCode session capture + continuity bootstrap.
20
20
 
21
21
  function getSessionId(event) {
@@ -172,41 +172,66 @@ export const ToolNetMemoryPlugin = async ({ directory }) => {
172
172
  },
173
173
  }
174
174
  }
175
- `;return v(o,n,{encoding:"utf8",mode:384}),o}import{existsSync as ee,mkdirSync as b,readFileSync as te,writeFileSync as Y}from"node:fs";import{homedir as H}from"node:os";import{dirname as $,join as O}from"node:path";function oe(t){let e=[],o=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=o.exec(t);){let n=r[1]??r[2]??"";try{e.push(r[1]!==void 0?JSON.parse(`"${n}"`):n)}catch{e.push(n)}}return e}function j(t={}){let e=t.configFile??O(process.env.CODEX_HOME??O(H(),".codex"),"config.toml"),o=t.previousFile??O(H(),".config","toolnet-memory","codex-notify-previous.json");b($(e),{recursive:!0}),b($(o),{recursive:!0});let r=ee(e)?te(e,"utf8"):"",n=t.binary??"toolnet-memory",a=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
176
- `),c=s.findIndex(i=>/^\s*\[/.test(i));c<0&&(c=s.length);let u=-1,d=-1;for(let i=0;i<c;i+=1)if(/^\s*notify\s*=/.test(s[i])){if(u=i,d=i,s[i].includes("[")&&!s[i].includes("]"))for(;d+1<c&&(d+=1,!s[d].includes("]")););break}let l=[];if(u>=0){let i=s.slice(u,d+1).join(`
177
- `);l=oe(i),s.splice(u,d-u+1,a)}else c=s.findIndex(i=>/^\s*\[/.test(i)),c<0&&(c=s.length),s.splice(c,0,a);let p=l.length>=2&&l[l.length-1]==="session:codex-notify";return l.length>0&&!p&&Y(o,JSON.stringify(l,null,2)+`
178
- `,{encoding:"utf8",mode:384}),r=s.join(`
175
+ `;return D(o,n,{encoding:"utf8",mode:384}),o}import{existsSync as ie,mkdirSync as k,readFileSync as se,writeFileSync as b}from"node:fs";import{homedir as H}from"node:os";import{dirname as P,join as T}from"node:path";function ce(e){let t=[],o=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=o.exec(e);){let n=r[1]??r[2]??"";try{t.push(r[1]!==void 0?JSON.parse(`"${n}"`):n)}catch{t.push(n)}}return t}function L(e={}){let t=e.configFile??T(process.env.CODEX_HOME??T(H(),".codex"),"config.toml"),o=e.previousFile??T(H(),".config","toolnet-memory","codex-notify-previous.json");k(P(t),{recursive:!0}),k(P(o),{recursive:!0});let r=ie(t)?se(t,"utf8"):"",n=e.binary??"toolnet-memory",s=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,i=r.split(`
176
+ `),a=i.findIndex(l=>/^\s*\[/.test(l));a<0&&(a=i.length);let f=-1,c=-1;for(let l=0;l<a;l+=1)if(/^\s*notify\s*=/.test(i[l])){if(f=l,c=l,i[l].includes("[")&&!i[l].includes("]"))for(;c+1<a&&(c+=1,!i[c].includes("]")););break}let g=[];if(f>=0){let l=i.slice(f,c+1).join(`
177
+ `);g=ce(l),i.splice(f,c-f+1,s)}else a=i.findIndex(l=>/^\s*\[/.test(l)),a<0&&(a=i.length),i.splice(a,0,s);let E=g.length>=2&&g[g.length-1]==="session:codex-notify";return g.length>0&&!E&&b(o,JSON.stringify(g,null,2)+`
178
+ `,{encoding:"utf8",mode:384}),r=i.join(`
179
179
  `),r.endsWith(`
180
180
  `)||(r+=`
181
- `),Y(e,r,{encoding:"utf8",mode:384}),{configFile:e,previousFile:o,preservedPrevious:l.length>0&&!p}}import{existsSync as ne,mkdirSync as re,readFileSync as se,writeFileSync as ie}from"node:fs";import{homedir as ce}from"node:os";import{dirname as ae,join as P}from"node:path";function le(t){return`'${t.replace(/'/g,"'\\''")}'`}function B(t={}){let e=t.hooksFile??P(process.env.CODEX_HOME??P(ce(),".codex"),"hooks.json");re(ae(e),{recursive:!0});let o={};if(ne(e))try{o=JSON.parse(se(e,"utf8"))}catch(c){throw new Error(`Invalid existing Codex hooks.json: ${c instanceof Error?c.message:String(c)}`)}let r=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?o.hooks:{};o.hooks=r;let a=(Array.isArray(r.SessionStart)?r.SessionStart:[]).filter(c=>{try{return!JSON.stringify(c).includes("session:codex-context")}catch{return!0}}),s=t.binary??"toolnet-memory";return a.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${le(s)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),r.SessionStart=a,ie(e,JSON.stringify(o,null,2)+`
182
- `,{encoding:"utf8",mode:384}),e}function A(t){return ge("sh",["-lc",`command -v ${JSON.stringify(t)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function de(){return A("agy")||h(x(C(),".gemini"))}function ue(){return A("opencode")||h(x(C(),".config","opencode"))}function fe(){return A("codex")||h(process.env.CODEX_HOME??x(C(),".codex"))}function T(t={}){let e=t.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=[];if(!(t.force===!0||de()))o.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let n=F({binary:e});o.push({agent:"agy",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"agy",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(t.force===!0||ue()))o.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let n=w({binary:e});o.push({agent:"opencode",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(t.force===!0||fe()))o.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let n=j({binary:e}),a=B({binary:e}),s=[n.configFile,a];n.preservedPrevious&&s.push(n.previousFile),o.push({agent:"codex",detected:!0,installed:!0,targets:s})}catch(n){o.push({agent:"codex",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}return o}function me(t){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let e of t){let o=e.agent==="agy"?"Agy / Antigravity":e.agent==="opencode"?"OpenCode":"Codex";if(!e.detected){console.log(`- ${o}: not detected`);continue}if(e.installed){console.log(`\u2713 ${o}: automatic memory enabled`);continue}console.log(`\u2717 ${o}: integration failed`),e.error&&console.log(` ${e.error}`)}console.log("")}async function pe(){let t=process.argv.slice(2),e=t.includes("--all"),o=t.includes("--json"),r=T({force:e});if(o){console.log(JSON.stringify(r,null,2));return}me(r)}var ye=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));ye&&pe().catch(t=>{console.error(t instanceof Error?t.message:String(t)),process.exitCode=1});var N=K.join(Ee.homedir(),".config","toolnet-memory"),m=K.join(N,".env");function _e(t){let e=new Map;for(let o of t.split(/\r?\n/)){let r=o.trim();if(!r||r.startsWith("#"))continue;let n=r.indexOf("=");n!==-1&&e.set(r.slice(0,n).trim(),r.slice(n+1).trim())}return e}function M(t){f.mkdirSync(N,{recursive:!0,mode:448});let e=`# ==========================================================
181
+ `),b(t,r,{encoding:"utf8",mode:384}),{configFile:t,previousFile:o,preservedPrevious:g.length>0&&!E}}import{existsSync as ae,mkdirSync as le,readFileSync as ge,writeFileSync as de}from"node:fs";import{homedir as ue}from"node:os";import{dirname as fe,join as U}from"node:path";function Ee(e){return`'${e.replace(/'/g,"'\\''")}'`}function v(e={}){let t=e.hooksFile??U(process.env.CODEX_HOME??U(ue(),".codex"),"hooks.json");le(fe(t),{recursive:!0});let o={};if(ae(t))try{o=JSON.parse(ge(t,"utf8"))}catch(a){throw new Error(`Invalid existing Codex hooks.json: ${a instanceof Error?a.message:String(a)}`)}let r=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?o.hooks:{};o.hooks=r;let s=(Array.isArray(r.SessionStart)?r.SessionStart:[]).filter(a=>{try{return!JSON.stringify(a).includes("session:codex-context")}catch{return!0}}),i=e.binary??"toolnet-memory";return s.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${Ee(i)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),r.SessionStart=s,de(t,JSON.stringify(o,null,2)+`
182
+ `,{encoding:"utf8",mode:384}),t}function h(e){return me("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function _e(){return h("agy")||A(N(R(),".gemini"))}function Se(){return h("opencode")||A(N(R(),".config","opencode"))}function pe(){return h("codex")||A(process.env.CODEX_HOME??N(R(),".codex"))}function I(e={}){let t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=[];if(!(e.force===!0||_e()))o.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let n=Y({binary:t});o.push({agent:"agy",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"agy",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(e.force===!0||Se()))o.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let n=w({binary:t});o.push({agent:"opencode",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(e.force===!0||pe()))o.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let n=L({binary:t}),s=v({binary:t}),i=[n.configFile,s];n.preservedPrevious&&i.push(n.previousFile),o.push({agent:"codex",detected:!0,installed:!0,targets:i})}catch(n){o.push({agent:"codex",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}return o}function ye(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let t of e){let o=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":"Codex";if(!t.detected){console.log(`- ${o}: not detected`);continue}if(t.installed){console.log(`\u2713 ${o}: automatic memory enabled`);continue}console.log(`\u2717 ${o}: integration failed`),t.error&&console.log(` ${t.error}`)}console.log("")}async function Ce(){let e=process.argv.slice(2),t=e.includes("--all"),o=e.includes("--json"),r=I({force:t});if(o){console.log(JSON.stringify(r,null,2));return}ye(r)}var Oe=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Oe&&Ce().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});var x=q.join(Te.homedir(),".config","toolnet-memory"),_=q.join(x,".env");function Re(e){let t=new Map;for(let o of e.split(/\r?\n/)){let r=o.trim();if(!r||r.startsWith("#"))continue;let n=r.indexOf("=");n!==-1&&t.set(r.slice(0,n).trim(),r.slice(n+1).trim())}return t}function J(e){let t=e.get("MEMORY_STORAGE_PROVIDER")?.trim();return t==="r2"||t==="s3"||t==="local"||t==="huggingface"?t:e.get("R2_ACCOUNT_ID")&&e.get("R2_BUCKET")?"r2":e.get("S3_BUCKET")?"s3":e.get("HF_NAMESPACE")&&e.get("HF_BUCKET")?"huggingface":"r2"}function B(e){return e==="r2"?["R2_ACCOUNT_ID","R2_BUCKET","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY"]:e==="s3"?["S3_BUCKET","S3_ACCESS_KEY_ID","S3_SECRET_ACCESS_KEY"]:e==="huggingface"?["HF_NAMESPACE","HF_BUCKET","HF_S3_ACCESS_KEY_ID","HF_S3_SECRET_ACCESS_KEY"]:[]}function y(e){return e==="r2"?"Cloudflare R2":e==="s3"?"S3-compatible storage":e==="huggingface"?"Hugging Face S3 (legacy)":"Local storage"}function j(e){m.mkdirSync(x,{recursive:!0,mode:448});let o=`# ==========================================================
183
183
  # TOOLNET MEMORY
184
184
  # ==========================================================
185
185
 
186
- MEMORY_STORAGE_PROVIDER=huggingface
187
-
188
- # Hugging Face
189
- HF_NAMESPACE=${t.get("HF_NAMESPACE")??""}
190
- HF_BUCKET=${t.get("HF_BUCKET")??"toolnet-memory"}
191
- HF_S3_ACCESS_KEY_ID=${t.get("HF_S3_ACCESS_KEY_ID")??""}
192
- HF_S3_SECRET_ACCESS_KEY=${t.get("HF_S3_SECRET_ACCESS_KEY")??""}
186
+ MEMORY_STORAGE_PROVIDER=${J(e)}
187
+
188
+ # Cloudflare R2
189
+ R2_ACCOUNT_ID=${e.get("R2_ACCOUNT_ID")??""}
190
+ R2_BUCKET=${e.get("R2_BUCKET")??"toolnet-memory"}
191
+ R2_ACCESS_KEY_ID=${e.get("R2_ACCESS_KEY_ID")??""}
192
+ R2_SECRET_ACCESS_KEY=${e.get("R2_SECRET_ACCESS_KEY")??""}
193
+
194
+ # Generic S3 / S3-compatible storage
195
+ # Leave S3_ENDPOINT empty for AWS S3.
196
+ S3_ENDPOINT=${e.get("S3_ENDPOINT")??""}
197
+ S3_REGION=${e.get("S3_REGION")??"us-east-1"}
198
+ S3_BUCKET=${e.get("S3_BUCKET")??"toolnet-memory"}
199
+ S3_ACCESS_KEY_ID=${e.get("S3_ACCESS_KEY_ID")??""}
200
+ S3_SECRET_ACCESS_KEY=${e.get("S3_SECRET_ACCESS_KEY")??""}
201
+ S3_FORCE_PATH_STYLE=${e.get("S3_FORCE_PATH_STYLE")??"false"}
202
+
203
+ # Hugging Face S3 (legacy compatibility)
204
+ HF_NAMESPACE=${e.get("HF_NAMESPACE")??""}
205
+ HF_BUCKET=${e.get("HF_BUCKET")??"toolnet-memory"}
206
+ HF_S3_ACCESS_KEY_ID=${e.get("HF_S3_ACCESS_KEY_ID")??""}
207
+ HF_S3_SECRET_ACCESS_KEY=${e.get("HF_S3_SECRET_ACCESS_KEY")??""}
208
+
209
+ # Embedding
210
+ HF_TOKEN=${e.get("HF_TOKEN")??""}
211
+ HF_EMBEDDING_MODEL=${e.get("HF_EMBEDDING_MODEL")??"sentence-transformers/all-MiniLM-L6-v2"}
193
212
 
194
213
  # Local cache
195
- MEMORY_LOCAL_STORAGE_PATH=${t.get("MEMORY_LOCAL_STORAGE_PATH")??""}
196
- MEMORY_LOCAL_CACHE_MB=${t.get("MEMORY_LOCAL_CACHE_MB")??"200"}
214
+ MEMORY_LOCAL_STORAGE_PATH=${e.get("MEMORY_LOCAL_STORAGE_PATH")??""}
215
+ MEMORY_LOCAL_CACHE_MB=${e.get("MEMORY_LOCAL_CACHE_MB")??"200"}
197
216
 
198
217
  # Automation
199
- MEMORY_AUTO_CAPTURE=${t.get("MEMORY_AUTO_CAPTURE")??"true"}
200
- MEMORY_AUTO_RETRIEVE=${t.get("MEMORY_AUTO_RETRIEVE")??"true"}
201
- MEMORY_AUTO_SUMMARIZE=${t.get("MEMORY_AUTO_SUMMARIZE")??"true"}
202
- MEMORY_AUTO_SYNC=${t.get("MEMORY_AUTO_SYNC")??"true"}
218
+ MEMORY_AUTO_CAPTURE=${e.get("MEMORY_AUTO_CAPTURE")??"true"}
219
+ MEMORY_AUTO_RETRIEVE=${e.get("MEMORY_AUTO_RETRIEVE")??"true"}
220
+ MEMORY_AUTO_SUMMARIZE=${e.get("MEMORY_AUTO_SUMMARIZE")??"true"}
221
+ MEMORY_AUTO_SYNC=${e.get("MEMORY_AUTO_SYNC")??"true"}
203
222
 
204
223
  # Retrieval
205
- MEMORY_MAX_CANDIDATES=${t.get("MEMORY_MAX_CANDIDATES")??"50"}
206
- MEMORY_RERANK_TOP=${t.get("MEMORY_RERANK_TOP")??"10"}
207
- MEMORY_FINAL_CONTEXT=${t.get("MEMORY_FINAL_CONTEXT")??"5"}
208
- MEMORY_TOKEN_BUDGET=${t.get("MEMORY_TOKEN_BUDGET")??"2000"}
209
- `;f.writeFileSync(m,e,{encoding:"utf8",mode:384}),f.chmodSync(N,448),f.chmodSync(m,384)}function D(t,e=!0){let o=t.trim().toLowerCase();return o?o==="y"||o==="yes":e}async function Oe(t){return g.isTTY?(y.write(t),new Promise(e=>{let o="",r=()=>{g.off("data",n),g.setRawMode?.(!1),g.pause(),y.write(`
210
- `),e(o)},n=a=>{for(let s of a.toString("utf8")){if(s==="\r"||s===`
211
- `){r();return}if(s===""&&(g.setRawMode?.(!1),y.write(`
212
- `),process.exit(130)),s==="\x7F"){o=o.slice(0,-1);continue}o+=s}};g.resume(),g.setRawMode?.(!0),g.on("data",n)})):""}function R(){try{let e=T().filter(o=>o.installed);if(e.length>0){console.log(""),console.log("Automatic AI memory:");for(let o of e){let r=o.agent==="agy"?"Agy / Antigravity":o.agent==="opencode"?"OpenCode":"Codex";console.log(` \u2713 ${r}`)}}}catch{}}async function he(){let t=f.existsSync(m),e=t?_e(f.readFileSync(m,"utf8")):new Map,o=["HF_NAMESPACE","HF_BUCKET","HF_S3_ACCESS_KEY_ID","HF_S3_SECRET_ACCESS_KEY"],r=o.every(i=>!!e.get(i)?.trim());if(console.log(""),console.log("TOOLNET MEMORY SETUP"),console.log("===================="),console.log(""),console.log(`Config: ${m}`),console.log(""),!g.isTTY||!y.isTTY){t||M(e),r?console.log("\u2713 Hugging Face storage already configured"):(console.log("Configuration pending."),console.log("Run:"),console.log(" toolnet-memory setup")),R();return}let n=Se.createInterface({input:g,output:y});if(r){console.log("\u2713 Hugging Face storage already configured"),console.log("");let i=await n.question("Use existing configuration? (Y/n) [Y]: ");if(D(i)){n.close(),console.log(""),console.log("\u2713 Existing Hugging Face configuration kept"),console.log(""),R(),console.log("Next:"),console.log(" toolnet-memory doctor");return}console.log("")}else{let i=await n.question("Configure Hugging Face storage now? (Y/n) [Y]: ");if(!D(i)){n.close(),t||M(e),console.log(""),console.log("Configuration pending."),console.log("Run later:"),console.log(" toolnet-memory setup");return}console.log("")}let a=e.get("HF_NAMESPACE")||"",s=e.get("HF_BUCKET")||"toolnet-memory",c=await n.question(a?`Hugging Face namespace [${a}]: `:"Hugging Face namespace: "),u=await n.question(`Bucket [${s}]: `),d=await n.question(e.get("HF_S3_ACCESS_KEY_ID")?"S3 Access Key ID [configured]: ":"S3 Access Key ID: ");n.close();let l=await Oe(e.get("HF_S3_SECRET_ACCESS_KEY")?"S3 Secret Access Key [configured]: ":"S3 Secret Access Key: ");c.trim()&&e.set("HF_NAMESPACE",c.trim()),u.trim()?e.set("HF_BUCKET",u.trim()):e.get("HF_BUCKET")||e.set("HF_BUCKET","toolnet-memory"),d.trim()&&e.set("HF_S3_ACCESS_KEY_ID",d.trim()),l.trim()&&e.set("HF_S3_SECRET_ACCESS_KEY",l.trim()),M(e);let p=o.filter(i=>!e.get(i)?.trim());if(console.log(""),console.log("\u2713 Configuration saved"),console.log(` ${m}`),console.log(""),p.length){console.log("Missing configuration:");for(let i of p)console.log(` - ${i}`);console.log(""),console.log("Run setup again:"),console.log(" toolnet-memory setup");return}console.log("\u2713 Hugging Face configuration complete"),console.log(""),R(),console.log("Next:"),console.log(" toolnet-memory doctor")}he().catch(t=>{console.error(t instanceof Error?t.message:String(t)),process.exit(1)});
224
+ MEMORY_MAX_CANDIDATES=${e.get("MEMORY_MAX_CANDIDATES")??"50"}
225
+ MEMORY_RERANK_TOP=${e.get("MEMORY_RERANK_TOP")??"10"}
226
+ MEMORY_FINAL_CONTEXT=${e.get("MEMORY_FINAL_CONTEXT")??"5"}
227
+ MEMORY_TOKEN_BUDGET=${e.get("MEMORY_TOKEN_BUDGET")??"2000"}
228
+
229
+ # Automatic Session Memory
230
+ TOOLNET_SESSION_LEARNING=${e.get("TOOLNET_SESSION_LEARNING")??"1"}
231
+ TOOLNET_WORK_CONTINUITY=${e.get("TOOLNET_WORK_CONTINUITY")??"1"}
232
+ TOOLNET_SEMANTIC_CONTINUITY=${e.get("TOOLNET_SEMANTIC_CONTINUITY")??"1"}
233
+ TOOLNET_SMART_HANDOFF=${e.get("TOOLNET_SMART_HANDOFF")??"1"}
234
+ `;m.writeFileSync(_,o,{encoding:"utf8",mode:384}),m.chmodSync(x,448),m.chmodSync(_,384)}function G(e,t=!0){let o=e.trim().toLowerCase();return o?o==="y"||o==="yes":t}async function Ne(e){return d.isTTY?(S.write(e),new Promise(t=>{let o="",r=()=>{d.off("data",n),d.setRawMode?.(!1),d.pause(),S.write(`
235
+ `),t(o)},n=s=>{for(let i of s.toString("utf8")){if(i==="\r"||i===`
236
+ `){r();return}if(i===""&&(d.setRawMode?.(!1),S.write(`
237
+ `),process.exit(130)),i==="\x7F"){o=o.slice(0,-1);continue}o+=i}};d.resume(),d.setRawMode?.(!0),d.on("data",n)})):""}function M(){try{let t=I().filter(o=>o.installed);if(t.length>0){console.log(""),console.log("Automatic AI memory:");for(let o of t){let r=o.agent==="agy"?"Agy / Antigravity":o.agent==="opencode"?"OpenCode":"Codex";console.log(` \u2713 ${r}`)}}}catch{}}function u(e,t,o,r){o.trim()?e.set(t,o.trim()):!e.get(t)&&r!==void 0&&e.set(t,r)}async function he(e,t){console.log("Storage provider:"),console.log(" 1. Cloudflare R2 (recommended)"),console.log(" 2. S3 / S3-compatible"),console.log(" 3. Local"),console.log(" 4. Hugging Face S3 (legacy)"),console.log("");let o=t==="s3"?"2":t==="local"?"3":t==="huggingface"?"4":"1",n=(await e.question(`Choose [${o}]: `)).trim()||o;return n==="2"?"s3":n==="3"?"local":n==="4"?"huggingface":"r2"}async function Ie(){let e=m.existsSync(_),t=e?Re(m.readFileSync(_,"utf8")):new Map,o=J(t),r=B(o),n=r.every(c=>!!t.get(c)?.trim());if(console.log(""),console.log("TOOLNET MEMORY SETUP"),console.log("===================="),console.log(""),console.log(`Config: ${_}`),console.log(""),!d.isTTY||!S.isTTY){e||(t.set("MEMORY_STORAGE_PROVIDER",o),j(t)),n?console.log(`\u2713 ${y(o)} already configured`):(console.log("Configuration pending."),console.log("Run:"),console.log(" toolnet-memory setup")),M();return}let s=Ae.createInterface({input:d,output:S});if(n){console.log(`\u2713 ${y(o)} already configured`),console.log("");let c=await s.question("Use existing storage configuration? (Y/n) [Y]: ");if(G(c)){s.close(),console.log(""),console.log(`\u2713 Existing ${y(o)} configuration kept`),console.log(""),M(),console.log("Next:"),console.log(" toolnet-memory doctor");return}console.log("")}o=await he(s,o),t.set("MEMORY_STORAGE_PROVIDER",o),console.log("");let i="",a="";if(o==="r2"){let c=await s.question(t.get("R2_ACCOUNT_ID")?`Cloudflare Account ID [${t.get("R2_ACCOUNT_ID")}]: `:"Cloudflare Account ID: "),g=await s.question(`R2 Bucket [${t.get("R2_BUCKET")||"toolnet-memory"}]: `),E=await s.question(t.get("R2_ACCESS_KEY_ID")?"R2 Access Key ID [configured]: ":"R2 Access Key ID: ");u(t,"R2_ACCOUNT_ID",c),u(t,"R2_BUCKET",g,"toolnet-memory"),u(t,"R2_ACCESS_KEY_ID",E),i="R2_SECRET_ACCESS_KEY",a=t.get(i)?"R2 Secret Access Key [configured]: ":"R2 Secret Access Key: "}else if(o==="s3"){let c=await s.question(t.get("S3_ENDPOINT")?`S3 Endpoint [${t.get("S3_ENDPOINT")}]: `:"S3 Endpoint [blank = AWS S3]: "),g=await s.question(`S3 Region [${t.get("S3_REGION")||"us-east-1"}]: `),E=await s.question(`S3 Bucket [${t.get("S3_BUCKET")||"toolnet-memory"}]: `),l=await s.question(t.get("S3_ACCESS_KEY_ID")?"S3 Access Key ID [configured]: ":"S3 Access Key ID: "),F=await s.question(`Force path-style? (y/N) [${t.get("S3_FORCE_PATH_STYLE")==="true"?"Y":"N"}]: `);c.trim()&&t.set("S3_ENDPOINT",c.trim()),u(t,"S3_REGION",g,"us-east-1"),u(t,"S3_BUCKET",E,"toolnet-memory"),u(t,"S3_ACCESS_KEY_ID",l),F.trim()?t.set("S3_FORCE_PATH_STYLE",G(F,!1)?"true":"false"):t.get("S3_FORCE_PATH_STYLE")||t.set("S3_FORCE_PATH_STYLE","false"),i="S3_SECRET_ACCESS_KEY",a=t.get(i)?"S3 Secret Access Key [configured]: ":"S3 Secret Access Key: "}else if(o==="huggingface"){let c=await s.question(t.get("HF_NAMESPACE")?`Hugging Face namespace [${t.get("HF_NAMESPACE")}]: `:"Hugging Face namespace: "),g=await s.question(`Bucket [${t.get("HF_BUCKET")||"toolnet-memory"}]: `),E=await s.question(t.get("HF_S3_ACCESS_KEY_ID")?"S3 Access Key ID [configured]: ":"S3 Access Key ID: ");u(t,"HF_NAMESPACE",c),u(t,"HF_BUCKET",g,"toolnet-memory"),u(t,"HF_S3_ACCESS_KEY_ID",E),i="HF_S3_SECRET_ACCESS_KEY",a=t.get(i)?"S3 Secret Access Key [configured]: ":"S3 Secret Access Key: "}if(s.close(),i){let c=await Ne(a);c.trim()&&t.set(i,c.trim())}j(t),r=B(o);let f=r.filter(c=>!t.get(c)?.trim());if(console.log(""),console.log("\u2713 Configuration saved"),console.log(` ${_}`),console.log(""),f.length){console.log("Missing configuration:");for(let c of f)console.log(` - ${c}`);console.log(""),console.log("Run setup again:"),console.log(" toolnet-memory setup");return}console.log(`\u2713 ${y(o)} configuration complete`),console.log(""),M(),console.log("Next:"),console.log(" toolnet-memory doctor")}Ie().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
@@ -1,4 +1,4 @@
1
- import"dotenv/config";import{existsSync as L,readFileSync as V}from"node:fs";import{homedir as K}from"node:os";import{join as J}from"node:path";function B(s){let e=s.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function F(){let s=process.env.TOOLNET_GLOBAL_ENV??J(K(),".config","toolnet-memory",".env");if(!L(s))return;let e=V(s,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let o=r.indexOf("=");if(o<=0)continue;let n=r.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&process.env[n]===void 0&&(process.env[n]=B(r.slice(o+1)))}}F();function y(s,e){return s===void 0?e:["1","true","yes","on"].includes(s.toLowerCase())}function g(s,e){if(!s)return e;let t=Number(s);return Number.isFinite(t)?t:e}function R(){return{memory:{autoCapture:y(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:y(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:y(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:y(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:g(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:g(process.env.MEMORY_RERANK_TOP,10),finalContext:g(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:g(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:g(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as U}from"node:crypto";import{existsSync as x,mkdirSync as H,readFileSync as Y,renameSync as G,writeFileSync as W}from"node:fs";import{basename as Z,dirname as h,join as v,parse as M,resolve as l}from"node:path";var T=".toolnet",Q="project.json";function X(s){return U("sha256").update(s).digest("hex").slice(0,16)}function P(s){return v(s,T,Q)}function q(s){return x(P(s))}function ee(s,e){let t=l(s),r=M(t).root;for(;;){if(q(t))return t;if(t===r||e&&t===l(e))break;let o=h(t);if(o===t)break;t=o}return null}function te(s){let e=l(s),t=M(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(n=>x(v(e,n))))return e;if(e===t)break;let o=h(e);if(o===e)break;e=o}return l(s)}function re(s){let e;try{e=JSON.parse(Y(s,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${s}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${s}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${s}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${s}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:h(h(s)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function A(s,e){let t=v(s,T);H(t,{recursive:!0});let r=P(s),o=`${r}.tmp-${process.pid}`;W(o,JSON.stringify(e,null,2)+`
3
- `,{encoding:"utf8",mode:384}),G(o,r)}function k(s,e){return{id:s.id,name:s.name,remote:s.remote,rootPath:e,createdAt:s.createdAt,updatedAt:s.updatedAt,graphVersion:s.graphVersion,memoryVersion:s.memoryVersion,metadata:s.metadata}}var j=class{detect(e=process.cwd()){let t=l(e),r=te(t),n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(m=>x(v(r,m))),i=ee(t,n?r:void 0);if(i){let m=P(i),d=re(m);return d.rootPath!==i&&(d.rootPath=i,d.updatedAt=new Date().toISOString(),A(i,d)),k(d,i)}let a=new Date().toISOString(),c=Z(r),p={version:1,id:X(r),name:c,remote:c,rootPath:r,createdAt:a,updatedAt:a,graphVersion:0,memoryVersion:0};return A(r,p),k(p,r)}};import{homedir as je}from"node:os";import{join as ve}from"node:path";import{DeleteObjectCommand as oe,GetObjectCommand as se,HeadObjectCommand as ne,ListObjectsV2Command as ie,PutObjectCommand as ae,S3Client as ce}from"@aws-sdk/client-s3";import{getSignedUrl as pe}from"@aws-sdk/s3-request-presigner";var S=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new ce({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new ae({Bucket:this.bucket,Key:e,Body:o,ContentType:r}))}async get(e){let t=await pe(this.client,new se({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new ne({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new oe({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let o=await this.client.send(new ie({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of o.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=o.IsTruncated?o.NextContinuationToken:void 0}while(r);return t}};import{access as E,mkdir as me,readFile as de,readdir as ue,rm as ge,stat as O,writeFile as le}from"node:fs/promises";import{dirname as fe,join as ye,relative as C,resolve as he}from"node:path";var f=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return he(this.root,t)}async put(e,t){let r=this.path(e);await me(fe(r),{recursive:!0}),await le(r,t)}async get(e){try{return await de(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await E(this.path(e)),!0}catch{return!1}}async delete(e){await ge(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),r=[];try{await E(t)}catch{return r}let o=async i=>{let a=await ue(i,{withFileTypes:!0});for(let c of a){let p=ye(i,c.name);if(c.isDirectory()){await o(p);continue}let m=await O(p);r.push({key:C(this.root,p),size:m.size,updatedAt:m.mtime.toISOString()})}},n=await O(t);return n.isDirectory()?await o(t):r.push({key:C(this.root,t),size:n.size,updatedAt:n.mtime.toISOString()}),r}};function I(s){let e=s.localRoot??ve(je(),".toolnet-memory","storage");if(s.provider==="huggingface"){let t=s.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new S({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):(console.warn("[storage] Hugging Face credentials missing. Using local fallback."),new f(e))}return new f(e)}function Se(s){return new Promise(e=>setTimeout(e,s))}async function _(s,e={}){let t=Math.max(1,e.attempts??3),r=e.baseDelayMs??150,o=e.maxDelayMs??2e3,n;for(let i=1;i<=t;i++)try{return await s()}catch(a){if(n=a,i>=t)break;let c=Math.min(o,r*2**(i-1)),p=Math.floor(Math.random()*Math.max(1,c*.2));await Se(c+p)}throw n}var we=new Set(["put","get","getText","delete","list"]);function $(s,e={}){return new Proxy(s,{get(t,r){let o=Reflect.get(t,r,t);return typeof o!="function"?o:we.has(r)?(...n)=>_(()=>Promise.resolve(o.apply(t,n)),e):o.bind(t)}})}function N(s){let e=s.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function z(s){let e=s.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let o=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:e))}var w=class{constructor(e,t,r,o){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=r,this.folder=N(o??r),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),r=t,o=await this.provider.getText(e);if(o){let i;try{i=JSON.parse(o)}catch(a){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${a instanceof Error?a.message:String(a)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let n={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:t};await this.provider.put(e,JSON.stringify(n,null,2)+`
4
- `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=z(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,r){return await this.ensureRegistered(),this.provider.put(this.key(e),t,r)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};var xe=["memories/current.json","vectors/current.json","graph/current.json","graph/manifest.json","graph/resolution/current.json","code/chunks/current.json","code/vectors/current.json","code/architecture/current.json","code/architecture/entry-points.json","code/architecture/hotspots.json","code/architecture/layers.json","code/architecture/clusters.json","code/analysis/current.json","code/analysis/dead-code.json","code/analysis/dependencies.json","code/visualization/graph.json"],u=class{constructor(e){this.storage=e}storage;projectPrefix(e){return`projects/${e}`}snapshotPrefix(e,t){return[this.projectPrefix(e),"snapshots",t].join("/")}async create(e,t="automatic",r){let o=new Date().toISOString(),n=o.replace(/[:.]/g,"-"),i=this.projectPrefix(e),a=this.snapshotPrefix(e,n),c=[];for(let m of xe){let d=`${i}/${m}`,b=await this.storage.get(d);b&&(await this.storage.put(`${a}/${m}`,b,"application/json"),c.push(m))}if(c.length===0)return null;let p={version:1,id:n,projectId:e,createdAt:o,reason:t,objects:c,metadata:r};return await this.storage.put(`${a}/snapshot.json`,JSON.stringify(p,null,2),"application/json"),p}async list(e){let t=[this.projectPrefix(e),"snapshots"].join("/"),o=(await this.storage.list(t)).filter(i=>i.key.endsWith("/snapshot.json")),n=[];for(let i of o){let a=await this.storage.getText(i.key);if(a)try{n.push(JSON.parse(a))}catch{}}return n.sort((i,a)=>a.createdAt.localeCompare(i.createdAt))}async restore(e,t){let r=this.snapshotPrefix(e,t),o=await this.storage.getText(`${r}/snapshot.json`);if(!o)throw new Error(`Snapshot not found: ${t}`);let n=JSON.parse(o);if(n.projectId!==e)throw new Error("Snapshot project mismatch");let i=this.projectPrefix(e),a=[];for(let c of n.objects){let p=await this.storage.get(`${r}/${c}`);p&&(await this.storage.put(`${i}/${c}`,p,"application/json"),a.push(c))}return{restored:a}}async remove(e,t){let r=this.snapshotPrefix(e,t),o=await this.storage.list(r);for(let n of o)await this.storage.delete(n.key);return o.length}async prune(e,t=10){let o=(await this.list(e)).slice(t),n=0;for(let i of o)await this.remove(e,i.id),n++;return n}};async function D(s,e){let t=new u(s),o=(await t.list(e))[0];if(!o)throw new Error("No snapshot available");let n=await t.restore(e,o.id);return{snapshot:o.id,createdAt:o.createdAt,...n}}async function Pe(){let s=process.argv[2],e=R(),t=new j().detect(),r=$(I({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3}),o=new w(r,t.id,t.name,t.remote??t.name),n=new u(o);if(s==="list"){let i=await n.list(t.id);console.log(i.map(a=>({id:a.id,createdAt:a.createdAt,reason:a.reason})));return}if(s==="create"){let i=process.argv.slice(3).join(" ")||"manual-cli";console.log(await n.create(t.id,i));return}if(s==="restore"){let i=process.argv[3];if(!i)throw new Error("snapshot id required");await n.create(t.id,"before-cli-restore"),console.log(await n.restore(t.id,i));return}if(s==="recover-latest"){console.log(await D(o,t.id));return}throw new Error("Usage: snapshot-cli list|create|restore|recover-latest")}Pe().catch(s=>{console.error(s instanceof Error?s.message:s),process.exit(1)});
1
+ import"dotenv/config";import{existsSync as B,readFileSync as V}from"node:fs";import{homedir as U}from"node:os";import{join as J}from"node:path";function F(o){let e=o.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Y(){let o=process.env.TOOLNET_GLOBAL_ENV??J(U(),".config","toolnet-memory",".env");if(!B(o))return;let e=V(o,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let s=r.indexOf("=");if(s<=0)continue;let n=r.slice(0,s).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&process.env[n]===void 0&&(process.env[n]=F(r.slice(s+1)))}}Y();function g(o,e){return o===void 0?e:["1","true","yes","on"].includes(o.toLowerCase())}function l(o,e){if(!o)return e;let t=Number(o);return Number.isFinite(t)?t:e}function R(){return{memory:{autoCapture:g(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:g(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:g(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:g(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:l(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:l(process.env.MEMORY_RERANK_TOP,10),finalContext:l(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:l(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:g(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:l(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as H}from"node:crypto";import{existsSync as P,mkdirSync as G,readFileSync as W,renameSync as Z,writeFileSync as Q}from"node:fs";import{basename as X,dirname as S,join as v,parse as T,resolve as f}from"node:path";var I=".toolnet",q="project.json";function ee(o){return H("sha256").update(o).digest("hex").slice(0,16)}function b(o){return v(o,I,q)}function te(o){return P(b(o))}function re(o,e){let t=f(o),r=T(t).root;for(;;){if(te(t))return t;if(t===r||e&&t===f(e))break;let s=S(t);if(s===t)break;t=s}return null}function oe(o){let e=f(o),t=T(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(n=>P(v(e,n))))return e;if(e===t)break;let s=S(e);if(s===e)break;e=s}return f(o)}function se(o){let e;try{e=JSON.parse(W(o,"utf8"))}catch(s){throw new Error(`Invalid ToolNet project manifest: ${o}: ${s instanceof Error?s.message:String(s)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${o}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${o}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${o}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:S(S(o)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function E(o,e){let t=v(o,I);G(t,{recursive:!0});let r=b(o),s=`${r}.tmp-${process.pid}`;Q(s,JSON.stringify(e,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),Z(s,r)}function C(o,e){return{id:o.id,name:o.name,remote:o.remote,rootPath:e,createdAt:o.createdAt,updatedAt:o.updatedAt,graphVersion:o.graphVersion,memoryVersion:o.memoryVersion,metadata:o.metadata}}var j=class{detect(e=process.cwd()){let t=f(e),r=oe(t),n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>P(v(r,d))),i=re(t,n?r:void 0);if(i){let d=b(i),u=se(d);return u.rootPath!==i&&(u.rootPath=i,u.updatedAt=new Date().toISOString(),E(i,u)),C(u,i)}let a=new Date().toISOString(),c=X(r),p={version:1,id:ee(r),name:c,remote:c,rootPath:r,createdAt:a,updatedAt:a,graphVersion:0,memoryVersion:0};return E(r,p),C(p,r)}};import{homedir as Re}from"node:os";import{join as Ee}from"node:path";import{DeleteObjectCommand as ne,GetObjectCommand as ie,HeadObjectCommand as ae,ListObjectsV2Command as ce,PutObjectCommand as pe,S3Client as de}from"@aws-sdk/client-s3";import{getSignedUrl as ue}from"@aws-sdk/s3-request-presigner";var w=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new de({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let s=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new pe({Bucket:this.bucket,Key:e,Body:s,ContentType:r}))}async get(e){let t=await ue(this.client,new ie({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new ae({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new ne({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let s=await this.client.send(new ce({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of s.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=s.IsTruncated?s.NextContinuationToken:void 0}while(r);return t}};import{access as M,mkdir as me,readFile as ge,readdir as le,rm as fe,stat as O,writeFile as ye}from"node:fs/promises";import{dirname as he,join as Se,relative as _,resolve as je}from"node:path";var y=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return je(this.root,t)}async put(e,t){let r=this.path(e);await me(he(r),{recursive:!0}),await ye(r,t)}async get(e){try{return await ge(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await M(this.path(e)),!0}catch{return!1}}async delete(e){await fe(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),r=[];try{await M(t)}catch{return r}let s=async i=>{let a=await le(i,{withFileTypes:!0});for(let c of a){let p=Se(i,c.name);if(c.isDirectory()){await s(p);continue}let d=await O(p);r.push({key:_(this.root,p),size:d.size,updatedAt:d.mtime.toISOString()})}},n=await O(t);return n.isDirectory()?await s(t):r.push({key:_(this.root,t),size:n.size,updatedAt:n.mtime.toISOString()}),r}};import{DeleteObjectCommand as ve,GetObjectCommand as we,HeadObjectCommand as xe,ListObjectsV2Command as Pe,PutObjectCommand as be,S3Client as ke}from"@aws-sdk/client-s3";import{getSignedUrl as Ae}from"@aws-sdk/s3-request-presigner";var h=class{name;client;bucket;constructor(e){this.name=e.name??"s3",this.bucket=e.bucket,this.client=new ke({region:e.region??"us-east-1",endpoint:e.endpoint||void 0,forcePathStyle:e.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,r="application/octet-stream"){let s=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new be({Bucket:this.bucket,Key:e,Body:s,ContentType:r}))}async get(e){let t=await Ae(this.client,new we({Bucket:this.bucket,Key:e}),{expiresIn:60}),r=await fetch(t,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`${this.name} download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new xe({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new ve({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],r;do{let s=await this.client.send(new Pe({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:r}));for(let n of s.Contents??[])n.Key&&t.push({key:n.Key,size:n.Size,updatedAt:n.LastModified?.toISOString()});r=s.IsTruncated?s.NextContinuationToken:void 0}while(r);return t}};function k(o,e){return console.warn(e),new y(o)}function $(o){let e=o.localRoot??Ee(Re(),".toolnet-memory","storage");if(o.provider==="r2"){let t=o.r2;return t?.accountId&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new h({name:"r2",endpoint:`https://${t.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:t.bucket,forcePathStyle:!0,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):k(e,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(o.provider==="s3"){let t=o.s3;return t?.bucket&&t.accessKeyId&&t.secretAccessKey?new h({name:"s3",endpoint:t.endpoint,region:t.region??"us-east-1",bucket:t.bucket,forcePathStyle:t.forcePathStyle??!1,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):k(e,"[storage] S3 credentials missing. Using local fallback.")}if(o.provider==="huggingface"){let t=o.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new w({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):k(e,"[storage] Hugging Face credentials missing. Using local fallback.")}return new y(e)}function Ce(o){return new Promise(e=>setTimeout(e,o))}async function K(o,e={}){let t=Math.max(1,e.attempts??3),r=e.baseDelayMs??150,s=e.maxDelayMs??2e3,n;for(let i=1;i<=t;i++)try{return await o()}catch(a){if(n=a,i>=t)break;let c=Math.min(s,r*2**(i-1)),p=Math.floor(Math.random()*Math.max(1,c*.2));await Ce(c+p)}throw n}var Te=new Set(["put","get","getText","delete","list"]);function N(o,e={}){return new Proxy(o,{get(t,r){let s=Reflect.get(t,r,t);return typeof s!="function"?s:Te.has(r)?(...n)=>K(()=>Promise.resolve(s.apply(t,n)),e):s.bind(t)}})}function D(o){let e=o.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function z(o){let e=o.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let s=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return s?`${s[1]}memory/records/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),s?`${s[1]}memory/vectors/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),s?`${s[1]}code/graph/${s[2]}`:e))}var x=class{constructor(e,t,r,s){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=r,this.folder=D(s??r),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),r=t,s=await this.provider.getText(e);if(s){let i;try{i=JSON.parse(s)}catch(a){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${a instanceof Error?a.message:String(a)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let n={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:t};await this.provider.put(e,JSON.stringify(n,null,2)+`
4
+ `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=z(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,r){return await this.ensureRegistered(),this.provider.put(this.key(e),t,r)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};var Ie=["memories/current.json","vectors/current.json","graph/current.json","graph/manifest.json","graph/resolution/current.json","code/chunks/current.json","code/vectors/current.json","code/architecture/current.json","code/architecture/entry-points.json","code/architecture/hotspots.json","code/architecture/layers.json","code/architecture/clusters.json","code/analysis/current.json","code/analysis/dead-code.json","code/analysis/dependencies.json","code/visualization/graph.json"],m=class{constructor(e){this.storage=e}storage;projectPrefix(e){return`projects/${e}`}snapshotPrefix(e,t){return[this.projectPrefix(e),"snapshots",t].join("/")}async create(e,t="automatic",r){let s=new Date().toISOString(),n=s.replace(/[:.]/g,"-"),i=this.projectPrefix(e),a=this.snapshotPrefix(e,n),c=[];for(let d of Ie){let u=`${i}/${d}`,A=await this.storage.get(u);A&&(await this.storage.put(`${a}/${d}`,A,"application/json"),c.push(d))}if(c.length===0)return null;let p={version:1,id:n,projectId:e,createdAt:s,reason:t,objects:c,metadata:r};return await this.storage.put(`${a}/snapshot.json`,JSON.stringify(p,null,2),"application/json"),p}async list(e){let t=[this.projectPrefix(e),"snapshots"].join("/"),s=(await this.storage.list(t)).filter(i=>i.key.endsWith("/snapshot.json")),n=[];for(let i of s){let a=await this.storage.getText(i.key);if(a)try{n.push(JSON.parse(a))}catch{}}return n.sort((i,a)=>a.createdAt.localeCompare(i.createdAt))}async restore(e,t){let r=this.snapshotPrefix(e,t),s=await this.storage.getText(`${r}/snapshot.json`);if(!s)throw new Error(`Snapshot not found: ${t}`);let n=JSON.parse(s);if(n.projectId!==e)throw new Error("Snapshot project mismatch");let i=this.projectPrefix(e),a=[];for(let c of n.objects){let p=await this.storage.get(`${r}/${c}`);p&&(await this.storage.put(`${i}/${c}`,p,"application/json"),a.push(c))}return{restored:a}}async remove(e,t){let r=this.snapshotPrefix(e,t),s=await this.storage.list(r);for(let n of s)await this.storage.delete(n.key);return s.length}async prune(e,t=10){let s=(await this.list(e)).slice(t),n=0;for(let i of s)await this.remove(e,i.id),n++;return n}};async function L(o,e){let t=new m(o),s=(await t.list(e))[0];if(!s)throw new Error("No snapshot available");let n=await t.restore(e,s.id);return{snapshot:s.id,createdAt:s.createdAt,...n}}async function Me(){let o=process.argv[2],e=R(),t=new j().detect(),r=N($({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3}),s=new x(r,t.id,t.name,t.remote??t.name),n=new m(s);if(o==="list"){let i=await n.list(t.id);console.log(i.map(a=>({id:a.id,createdAt:a.createdAt,reason:a.reason})));return}if(o==="create"){let i=process.argv.slice(3).join(" ")||"manual-cli";console.log(await n.create(t.id,i));return}if(o==="restore"){let i=process.argv[3];if(!i)throw new Error("snapshot id required");await n.create(t.id,"before-cli-restore"),console.log(await n.restore(t.id,i));return}if(o==="recover-latest"){console.log(await L(s,t.id));return}throw new Error("Usage: snapshot-cli list|create|restore|recover-latest")}Me().catch(o=>{console.error(o instanceof Error?o.message:o),process.exit(1)});
@@ -1,12 +1,12 @@
1
- import{existsSync as ne,readFileSync as ie}from"node:fs";import{homedir as ae}from"node:os";import{join as ce}from"node:path";function pe(r){let e=r.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function de(){let r=process.env.TOOLNET_GLOBAL_ENV??ce(ae(),".config","toolnet-memory",".env");if(!ne(r))return;let e=ie(r,"utf8");for(let t of e.split(/\r?\n/)){let o=t.trim();if(!o||o.startsWith("#"))continue;o.startsWith("export ")&&(o=o.slice(7));let s=o.indexOf("=");if(s<=0)continue;let i=o.slice(0,s).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&process.env[i]===void 0&&(process.env[i]=pe(o.slice(s+1)))}}de();function v(r,e){return r===void 0?e:["1","true","yes","on"].includes(r.toLowerCase())}function h(r,e){if(!r)return e;let t=Number(r);return Number.isFinite(t)?t:e}function D(){return{memory:{autoCapture:v(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:v(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:v(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:v(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:h(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:h(process.env.MEMORY_RERANK_TOP,10),finalContext:h(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:h(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:h(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as ue}from"node:crypto";import{existsSync as M,mkdirSync as me,readFileSync as le,renameSync as ge,writeFileSync as fe}from"node:fs";import{basename as ye,dirname as x,join as b,parse as W,resolve as S}from"node:path";var K=".toolnet",he="project.json";function Se(r){return ue("sha256").update(r).digest("hex").slice(0,16)}function I(r){return b(r,K,he)}function je(r){return M(I(r))}function ke(r,e){let t=S(r),o=W(t).root;for(;;){if(je(t))return t;if(t===o||e&&t===S(e))break;let s=x(t);if(s===t)break;t=s}return null}function ve(r){let e=S(r),t=W(e).root,o=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(o.some(i=>M(b(e,i))))return e;if(e===t)break;let s=x(e);if(s===e)break;e=s}return S(r)}function xe(r){let e;try{e=JSON.parse(le(r,"utf8"))}catch(s){throw new Error(`Invalid ToolNet project manifest: ${r}: ${s instanceof Error?s.message:String(s)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let o=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:x(x(r)),createdAt:typeof t.createdAt=="string"?t.createdAt:o,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:o,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function L(r,e){let t=b(r,K);me(t,{recursive:!0});let o=I(r),s=`${o}.tmp-${process.pid}`;fe(s,JSON.stringify(e,null,2)+`
3
- `,{encoding:"utf8",mode:384}),ge(s,o)}function V(r,e){return{id:r.id,name:r.name,remote:r.remote,rootPath:e,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}var w=class{detect(e=process.cwd()){let t=S(e),o=ve(t),i=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(u=>M(b(o,u))),a=ke(t,i?o:void 0);if(a){let u=I(a),l=xe(u);return l.rootPath!==a&&(l.rootPath=a,l.updatedAt=new Date().toISOString(),L(a,l)),V(l,a)}let c=new Date().toISOString(),p=ye(o),d={version:1,id:Se(o),name:p,remote:p,rootPath:o,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0};return L(o,d),V(d,o)}};import{homedir as De}from"node:os";import{join as Le}from"node:path";import{DeleteObjectCommand as we,GetObjectCommand as be,HeadObjectCommand as Pe,ListObjectsV2Command as Ae,PutObjectCommand as Re,S3Client as Te}from"@aws-sdk/client-s3";import{getSignedUrl as Me}from"@aws-sdk/s3-request-presigner";var P=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new Te({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,o="application/octet-stream"){let s=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Re({Bucket:this.bucket,Key:e,Body:s,ContentType:o}))}async get(e){let t=await Me(this.client,new be({Bucket:this.bucket,Key:e}),{expiresIn:60}),o=await fetch(t,{redirect:"follow"});if(o.status===404)return null;if(!o.ok)throw new Error(`HF download failed: ${o.status} ${o.statusText}`);return new Uint8Array(await o.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new Pe({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new we({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],o;do{let s=await this.client.send(new Ae({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:o}));for(let i of s.Contents??[])i.Key&&t.push({key:i.Key,size:i.Size,updatedAt:i.LastModified?.toISOString()});o=s.IsTruncated?s.NextContinuationToken:void 0}while(o);return t}};import{access as J,mkdir as Ie,readFile as Oe,readdir as Ee,rm as Ce,stat as B,writeFile as _e}from"node:fs/promises";import{dirname as Ne,join as $e,relative as F,resolve as ze}from"node:path";var j=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return ze(this.root,t)}async put(e,t){let o=this.path(e);await Ie(Ne(o),{recursive:!0}),await _e(o,t)}async get(e){try{return await Oe(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await J(this.path(e)),!0}catch{return!1}}async delete(e){await Ce(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),o=[];try{await J(t)}catch{return o}let s=async a=>{let c=await Ee(a,{withFileTypes:!0});for(let p of c){let d=$e(a,p.name);if(p.isDirectory()){await s(d);continue}let u=await B(d);o.push({key:F(this.root,d),size:u.size,updatedAt:u.mtime.toISOString()})}},i=await B(t);return i.isDirectory()?await s(t):o.push({key:F(this.root,t),size:i.size,updatedAt:i.mtime.toISOString()}),o}};function H(r){let e=r.localRoot??Le(De(),".toolnet-memory","storage");if(r.provider==="huggingface"){let t=r.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new P({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):(console.warn("[storage] Hugging Face credentials missing. Using local fallback."),new j(e))}return new j(e)}function Ve(r){return new Promise(e=>setTimeout(e,r))}async function G(r,e={}){let t=Math.max(1,e.attempts??3),o=e.baseDelayMs??150,s=e.maxDelayMs??2e3,i;for(let a=1;a<=t;a++)try{return await r()}catch(c){if(i=c,a>=t)break;let p=Math.min(s,o*2**(a-1)),d=Math.floor(Math.random()*Math.max(1,p*.2));await Ve(p+d)}throw i}var We=new Set(["put","get","getText","delete","list"]);function U(r,e={}){return new Proxy(r,{get(t,o){let s=Reflect.get(t,o,t);return typeof s!="function"?s:We.has(o)?(...i)=>G(()=>Promise.resolve(s.apply(t,i)),e):s.bind(t)}})}function Y(r){let e=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function Z(r){let e=r.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(o)return`${o[1]}memory/records/${o[2]}`;if(o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),o)return`${o[1]}memory/vectors/${o[2]}`;if(o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),o)return`${o[1]}code/graph/${o[2]}`;let s=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return s?`${s[1]}memory/records/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),s?`${s[1]}memory/vectors/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),s?`${s[1]}code/graph/${s[2]}`:e))}var A=class{constructor(e,t,o,s){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=o,this.folder=Y(s??o),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),o=t,s=await this.provider.getText(e);if(s){let a;try{a=JSON.parse(s)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof a.id=="string"&&a.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${a.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof a.createdAt=="string"&&(o=a.createdAt)}let i={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:o,updatedAt:t};await this.provider.put(e,JSON.stringify(i,null,2)+`
4
- `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=Z(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,o){return await this.ensureRegistered(),this.provider.put(this.key(e),t,o)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import{join as q}from"node:path";import{mkdirSync as Ge}from"node:fs";import{createHash as Ke}from"node:crypto";import{dirname as Je}from"node:path";import{mkdirSync as Be,readFileSync as Lr,renameSync as Fe,writeFileSync as He}from"node:fs";function X(r){return Ke("sha256").update(r).digest("hex")}function Q(r,e){Be(Je(r),{recursive:!0});let t=`${r}.${process.pid}.tmp`;He(t,JSON.stringify(e,null,2)+`
5
- `,{encoding:"utf8",mode:384}),Fe(t,r)}function Ue(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function y(r,e=20){let t=[],o=new Set;for(let s of r.slice().reverse()){let i=Ue(s);if(!(!i||o.has(i))&&(o.add(i),t.push(s),t.length>=e))break}return t.reverse()}function Ye(r){return r.kind==="phase"?/^Phase\s+\d+$/iu.test(r.text):r.kind==="task"?/^(?:TODO|Task|Việc)\s+\d+$/iu.test(r.text):!1}function ee(r,e){let t=e.status??r?.status??"pending",o=t;r&&(r.status==="completed"&&t!=="completed"?o="completed":t==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(o=r.status));let s=r&&Ye(e)?r.title:e.text;return{id:r?.id??X(e.key).slice(0,24),title:s,status:o,order:e.order??r?.order,confidence:Math.max(e.confidence,r?.confidence??0),updatedAt:e.occurredAt,updatedBy:{agent:e.agent,nativeSessionId:e.nativeSessionId,eventId:e.eventId}}}async function Ze(r,e){let t=`projects/${r.id}/work/observations/`,o=await e.list(t),s=[];for(let i of o.filter(a=>a.key.endsWith(".json")).sort((a,c)=>a.key.localeCompare(c.key))){let a=await e.getText(i.key);if(a)try{let c=JSON.parse(a);c.version===1&&Array.isArray(c.observations)&&s.push(c)}catch{}}return s}async function te(r,e){let o=(await Ze(r,e)).flatMap(n=>n.observations).sort((n,f)=>{let $=n.occurredAt.localeCompare(f.occurredAt);if($!==0)return $;let z=n.sequence-f.sequence;return z!==0?z:n.id.localeCompare(f.id)}),s=new Map,i=new Map,a,c,p,d=[],u=[],l=[],E=[],C=[],_=[];for(let n of o)switch(n.kind){case"goal":a=n.text;break;case"plan":c=n.text;break;case"phase":s.set(n.key,ee(s.get(n.key),n));break;case"task":i.set(n.key,ee(i.get(n.key),n));break;case"decision":d.push(n.text);break;case"blocker":u.push(n.text);break;case"warning":l.push(n.text);break;case"next_action":E.push(n.text);break;case"file":C.push(n.text);break;case"test":_.push(n.text);break;case"session":p={agent:n.agent,nativeSessionId:n.nativeSessionId,sessionKey:n.sessionKey,updatedAt:n.occurredAt};break}let g=Array.from(s.values()).sort((n,f)=>(n.order??Number.MAX_SAFE_INTEGER)-(f.order??Number.MAX_SAFE_INTEGER)),m=Array.from(i.values()).sort((n,f)=>(n.order??Number.MAX_SAFE_INTEGER)-(f.order??Number.MAX_SAFE_INTEGER)),R=g.find(n=>n.status==="in_progress")??g.find(n=>n.status==="blocked")??g.find(n=>n.status==="pending"),k=m.find(n=>n.status==="in_progress")??m.find(n=>n.status==="blocked")??m.find(n=>n.status==="pending"),oe=y([...E,...k?[k.title]:[],...!k&&R?[R.title]:[],...m.filter(n=>n.status==="pending").slice(0,5).map(n=>n.title)],8),se=y([...u,...g.filter(n=>n.status==="blocked").map(n=>n.title),...m.filter(n=>n.status==="blocked").map(n=>n.title)],20),T={version:1,projectId:r.id,projectName:r.name,goal:a,plan:c,phases:g,tasks:m,decisions:y(d,20),blockers:se,warnings:y(l,20),nextActions:oe,filesTouched:y(C,30),tests:y(_,20),currentPhase:R,currentTask:k,progress:{phasesTotal:g.length,phasesCompleted:g.filter(n=>n.status==="completed").length,tasksTotal:m.length,tasksCompleted:m.filter(n=>n.status==="completed").length,blocked:g.filter(n=>n.status==="blocked").length+m.filter(n=>n.status==="blocked").length},lastSession:p,updatedAt:o.length?o[o.length-1].occurredAt:new Date().toISOString()},N=q(r.rootPath,".toolnet","work");return Ge(N,{recursive:!0}),Q(q(N,"current.json"),T),await e.put(`projects/${r.id}/work/current.json`,JSON.stringify(T,null,2)+`
6
- `,"application/json"),T}async function O(r,e){let t=await e.getText(`projects/${r.id}/work/current.json`);if(!t)return null;try{return JSON.parse(t)}catch{return null}}function Xe(r,e){let t=r.indexOf(e);return t>=0?r[t+1]:void 0}function Qe(r){let e=D(),t=U(H({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3});return new A(t,r.id,r.name,r.remote??r.name)}function re(r){console.log(),console.log(`Project: ${r.projectName}`),r.goal&&console.log(`Goal: ${r.goal}`),r.currentPhase&&console.log(`Current phase: ${r.currentPhase.title} [${r.currentPhase.status}]`),r.currentTask&&console.log(`Current task: ${r.currentTask.title} [${r.currentTask.status}]`),console.log(`Phases: ${r.progress.phasesCompleted}/${r.progress.phasesTotal}`),console.log(`Tasks: ${r.progress.tasksCompleted}/${r.progress.tasksTotal}`),r.nextActions.length&&(console.log(),console.log("Next actions:"),r.nextActions.forEach((e,t)=>console.log(`${t+1}. ${e}`))),r.blockers.length&&(console.log(),console.log("Blockers:"),r.blockers.forEach(e=>console.log(`- ${e}`))),r.lastSession&&(console.log(),console.log(`Last session: ${r.lastSession.agent} / ${r.lastSession.nativeSessionId}`))}async function qe(){let[r="help",...e]=process.argv.slice(2),t=new w().detect(Xe(e,"--project")??process.cwd()),o=Qe(t);if(r==="reconcile"){let s=await te(t,o);re(s);return}if(r==="status"){let s=await O(t,o);if(!s){console.log("No active work state yet.");return}re(s);return}if(r==="json"){let s=await O(t,o);console.log(JSON.stringify(s,null,2));return}console.log(`Work Continuity
1
+ import{existsSync as ae,readFileSync as ce}from"node:fs";import{homedir as pe}from"node:os";import{join as ue}from"node:path";function de(r){let e=r.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function le(){let r=process.env.TOOLNET_GLOBAL_ENV??ue(pe(),".config","toolnet-memory",".env");if(!ae(r))return;let e=ce(r,"utf8");for(let t of e.split(/\r?\n/)){let o=t.trim();if(!o||o.startsWith("#"))continue;o.startsWith("export ")&&(o=o.slice(7));let s=o.indexOf("=");if(s<=0)continue;let i=o.slice(0,s).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&process.env[i]===void 0&&(process.env[i]=de(o.slice(s+1)))}}le();function h(r,e){return r===void 0?e:["1","true","yes","on"].includes(r.toLowerCase())}function S(r,e){if(!r)return e;let t=Number(r);return Number.isFinite(t)?t:e}function B(){return{memory:{autoCapture:h(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:h(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:h(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:h(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:S(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:S(process.env.MEMORY_RERANK_TOP,10),finalContext:S(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:S(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:h(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:S(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as me}from"node:crypto";import{existsSync as E,mkdirSync as ge,readFileSync as fe,renameSync as ye,writeFileSync as he}from"node:fs";import{basename as Se,dirname as x,join as P,parse as W,resolve as k}from"node:path";var J=".toolnet",ke="project.json";function je(r){return me("sha256").update(r).digest("hex").slice(0,16)}function C(r){return P(r,J,ke)}function ve(r){return E(C(r))}function we(r,e){let t=k(r),o=W(t).root;for(;;){if(ve(t))return t;if(t===o||e&&t===k(e))break;let s=x(t);if(s===t)break;t=s}return null}function xe(r){let e=k(r),t=W(e).root,o=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(o.some(i=>E(P(e,i))))return e;if(e===t)break;let s=x(e);if(s===e)break;e=s}return k(r)}function be(r){let e;try{e=JSON.parse(fe(r,"utf8"))}catch(s){throw new Error(`Invalid ToolNet project manifest: ${r}: ${s instanceof Error?s.message:String(s)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let o=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:x(x(r)),createdAt:typeof t.createdAt=="string"?t.createdAt:o,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:o,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function L(r,e){let t=P(r,J);ge(t,{recursive:!0});let o=C(r),s=`${o}.tmp-${process.pid}`;he(s,JSON.stringify(e,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),ye(s,o)}function V(r,e){return{id:r.id,name:r.name,remote:r.remote,rootPath:e,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}var b=class{detect(e=process.cwd()){let t=k(e),o=xe(t),i=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>E(P(o,d))),a=we(t,i?o:void 0);if(a){let d=C(a),m=be(d);return m.rootPath!==a&&(m.rootPath=a,m.updatedAt=new Date().toISOString(),L(a,m)),V(m,a)}let c=new Date().toISOString(),p=Se(o),u={version:1,id:je(o),name:p,remote:p,rootPath:o,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0};return L(o,u),V(u,o)}};import{homedir as He}from"node:os";import{join as Ye}from"node:path";import{DeleteObjectCommand as Pe,GetObjectCommand as Ae,HeadObjectCommand as Re,ListObjectsV2Command as Te,PutObjectCommand as Ie,S3Client as Ee}from"@aws-sdk/client-s3";import{getSignedUrl as Ce}from"@aws-sdk/s3-request-presigner";var A=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new Ee({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,o="application/octet-stream"){let s=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Ie({Bucket:this.bucket,Key:e,Body:s,ContentType:o}))}async get(e){let t=await Ce(this.client,new Ae({Bucket:this.bucket,Key:e}),{expiresIn:60}),o=await fetch(t,{redirect:"follow"});if(o.status===404)return null;if(!o.ok)throw new Error(`HF download failed: ${o.status} ${o.statusText}`);return new Uint8Array(await o.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new Re({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Pe({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],o;do{let s=await this.client.send(new Te({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:o}));for(let i of s.Contents??[])i.Key&&t.push({key:i.Key,size:i.Size,updatedAt:i.LastModified?.toISOString()});o=s.IsTruncated?s.NextContinuationToken:void 0}while(o);return t}};import{access as F,mkdir as Oe,readFile as Me,readdir as _e,rm as Ne,stat as U,writeFile as $e}from"node:fs/promises";import{dirname as Ke,join as De,relative as H,resolve as ze}from"node:path";var j=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return ze(this.root,t)}async put(e,t){let o=this.path(e);await Oe(Ke(o),{recursive:!0}),await $e(o,t)}async get(e){try{return await Me(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await F(this.path(e)),!0}catch{return!1}}async delete(e){await Ne(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),o=[];try{await F(t)}catch{return o}let s=async a=>{let c=await _e(a,{withFileTypes:!0});for(let p of c){let u=De(a,p.name);if(p.isDirectory()){await s(u);continue}let d=await U(u);o.push({key:H(this.root,u),size:d.size,updatedAt:d.mtime.toISOString()})}},i=await U(t);return i.isDirectory()?await s(t):o.push({key:H(this.root,t),size:i.size,updatedAt:i.mtime.toISOString()}),o}};import{DeleteObjectCommand as Be,GetObjectCommand as Le,HeadObjectCommand as Ve,ListObjectsV2Command as We,PutObjectCommand as Je,S3Client as Fe}from"@aws-sdk/client-s3";import{getSignedUrl as Ue}from"@aws-sdk/s3-request-presigner";var v=class{name;client;bucket;constructor(e){this.name=e.name??"s3",this.bucket=e.bucket,this.client=new Fe({region:e.region??"us-east-1",endpoint:e.endpoint||void 0,forcePathStyle:e.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,o="application/octet-stream"){let s=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Je({Bucket:this.bucket,Key:e,Body:s,ContentType:o}))}async get(e){let t=await Ue(this.client,new Le({Bucket:this.bucket,Key:e}),{expiresIn:60}),o=await fetch(t,{redirect:"follow"});if(o.status===404)return null;if(!o.ok)throw new Error(`${this.name} download failed: ${o.status} ${o.statusText}`);return new Uint8Array(await o.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new Ve({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Be({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],o;do{let s=await this.client.send(new We({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:o}));for(let i of s.Contents??[])i.Key&&t.push({key:i.Key,size:i.Size,updatedAt:i.LastModified?.toISOString()});o=s.IsTruncated?s.NextContinuationToken:void 0}while(o);return t}};function O(r,e){return console.warn(e),new j(r)}function Y(r){let e=r.localRoot??Ye(He(),".toolnet-memory","storage");if(r.provider==="r2"){let t=r.r2;return t?.accountId&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new v({name:"r2",endpoint:`https://${t.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:t.bucket,forcePathStyle:!0,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):O(e,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(r.provider==="s3"){let t=r.s3;return t?.bucket&&t.accessKeyId&&t.secretAccessKey?new v({name:"s3",endpoint:t.endpoint,region:t.region??"us-east-1",bucket:t.bucket,forcePathStyle:t.forcePathStyle??!1,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):O(e,"[storage] S3 credentials missing. Using local fallback.")}if(r.provider==="huggingface"){let t=r.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new A({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):O(e,"[storage] Hugging Face credentials missing. Using local fallback.")}return new j(e)}function Ge(r){return new Promise(e=>setTimeout(e,r))}async function G(r,e={}){let t=Math.max(1,e.attempts??3),o=e.baseDelayMs??150,s=e.maxDelayMs??2e3,i;for(let a=1;a<=t;a++)try{return await r()}catch(c){if(i=c,a>=t)break;let p=Math.min(s,o*2**(a-1)),u=Math.floor(Math.random()*Math.max(1,p*.2));await Ge(p+u)}throw i}var Ze=new Set(["put","get","getText","delete","list"]);function Z(r,e={}){return new Proxy(r,{get(t,o){let s=Reflect.get(t,o,t);return typeof s!="function"?s:Ze.has(o)?(...i)=>G(()=>Promise.resolve(s.apply(t,i)),e):s.bind(t)}})}function Q(r){let e=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function X(r){let e=r.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(o)return`${o[1]}memory/records/${o[2]}`;if(o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),o)return`${o[1]}memory/vectors/${o[2]}`;if(o=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),o)return`${o[1]}code/graph/${o[2]}`;let s=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return s?`${s[1]}memory/records/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),s?`${s[1]}memory/vectors/${s[2]}`:(s=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),s?`${s[1]}code/graph/${s[2]}`:e))}var R=class{constructor(e,t,o,s){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=o,this.folder=Q(s??o),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),o=t,s=await this.provider.getText(e);if(s){let a;try{a=JSON.parse(s)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof a.id=="string"&&a.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${a.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof a.createdAt=="string"&&(o=a.createdAt)}let i={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:o,updatedAt:t};await this.provider.put(e,JSON.stringify(i,null,2)+`
4
+ `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=X(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,o){return await this.ensureRegistered(),this.provider.put(this.key(e),t,o)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import{join as te}from"node:path";import{mkdirSync as rt}from"node:fs";import{createHash as Qe}from"node:crypto";import{dirname as Xe}from"node:path";import{mkdirSync as qe,readFileSync as to,renameSync as et,writeFileSync as tt}from"node:fs";function q(r){return Qe("sha256").update(r).digest("hex")}function ee(r,e){qe(Xe(r),{recursive:!0});let t=`${r}.${process.pid}.tmp`;tt(t,JSON.stringify(e,null,2)+`
5
+ `,{encoding:"utf8",mode:384}),et(t,r)}function ot(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function y(r,e=20){let t=[],o=new Set;for(let s of r.slice().reverse()){let i=ot(s);if(!(!i||o.has(i))&&(o.add(i),t.push(s),t.length>=e))break}return t.reverse()}function st(r){return r.kind==="phase"?/^Phase\s+\d+$/iu.test(r.text):r.kind==="task"?/^(?:TODO|Task|Việc)\s+\d+$/iu.test(r.text):!1}function re(r,e){let t=e.status??r?.status??"pending",o=t;r&&(r.status==="completed"&&t!=="completed"?o="completed":t==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(o=r.status));let s=r&&st(e)?r.title:e.text;return{id:r?.id??q(e.key).slice(0,24),title:s,status:o,order:e.order??r?.order,confidence:Math.max(e.confidence,r?.confidence??0),updatedAt:e.occurredAt,updatedBy:{agent:e.agent,nativeSessionId:e.nativeSessionId,eventId:e.eventId}}}async function nt(r,e){let t=`projects/${r.id}/work/observations/`,o=await e.list(t),s=[];for(let i of o.filter(a=>a.key.endsWith(".json")).sort((a,c)=>a.key.localeCompare(c.key))){let a=await e.getText(i.key);if(a)try{let c=JSON.parse(a);c.version===1&&Array.isArray(c.observations)&&s.push(c)}catch{}}return s}async function oe(r,e){let o=(await nt(r,e)).flatMap(n=>n.observations).sort((n,f)=>{let D=n.occurredAt.localeCompare(f.occurredAt);if(D!==0)return D;let z=n.sequence-f.sequence;return z!==0?z:n.id.localeCompare(f.id)}),s=new Map,i=new Map,a,c,p,u=[],d=[],m=[],_=[],N=[],$=[];for(let n of o)switch(n.kind){case"goal":a=n.text;break;case"plan":c=n.text;break;case"phase":s.set(n.key,re(s.get(n.key),n));break;case"task":i.set(n.key,re(i.get(n.key),n));break;case"decision":u.push(n.text);break;case"blocker":d.push(n.text);break;case"warning":m.push(n.text);break;case"next_action":_.push(n.text);break;case"file":N.push(n.text);break;case"test":$.push(n.text);break;case"session":p={agent:n.agent,nativeSessionId:n.nativeSessionId,sessionKey:n.sessionKey,updatedAt:n.occurredAt};break}let g=Array.from(s.values()).sort((n,f)=>(n.order??Number.MAX_SAFE_INTEGER)-(f.order??Number.MAX_SAFE_INTEGER)),l=Array.from(i.values()).sort((n,f)=>(n.order??Number.MAX_SAFE_INTEGER)-(f.order??Number.MAX_SAFE_INTEGER)),T=g.find(n=>n.status==="in_progress")??g.find(n=>n.status==="blocked")??g.find(n=>n.status==="pending"),w=l.find(n=>n.status==="in_progress")??l.find(n=>n.status==="blocked")??l.find(n=>n.status==="pending"),ne=y([..._,...w?[w.title]:[],...!w&&T?[T.title]:[],...l.filter(n=>n.status==="pending").slice(0,5).map(n=>n.title)],8),ie=y([...d,...g.filter(n=>n.status==="blocked").map(n=>n.title),...l.filter(n=>n.status==="blocked").map(n=>n.title)],20),I={version:1,projectId:r.id,projectName:r.name,goal:a,plan:c,phases:g,tasks:l,decisions:y(u,20),blockers:ie,warnings:y(m,20),nextActions:ne,filesTouched:y(N,30),tests:y($,20),currentPhase:T,currentTask:w,progress:{phasesTotal:g.length,phasesCompleted:g.filter(n=>n.status==="completed").length,tasksTotal:l.length,tasksCompleted:l.filter(n=>n.status==="completed").length,blocked:g.filter(n=>n.status==="blocked").length+l.filter(n=>n.status==="blocked").length},lastSession:p,updatedAt:o.length?o[o.length-1].occurredAt:new Date().toISOString()},K=te(r.rootPath,".toolnet","work");return rt(K,{recursive:!0}),ee(te(K,"current.json"),I),await e.put(`projects/${r.id}/work/current.json`,JSON.stringify(I,null,2)+`
6
+ `,"application/json"),I}async function M(r,e){let t=await e.getText(`projects/${r.id}/work/current.json`);if(!t)return null;try{return JSON.parse(t)}catch{return null}}function it(r,e){let t=r.indexOf(e);return t>=0?r[t+1]:void 0}function at(r){let e=B(),t=Z(Y({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3});return new R(t,r.id,r.name,r.remote??r.name)}function se(r){console.log(),console.log(`Project: ${r.projectName}`),r.goal&&console.log(`Goal: ${r.goal}`),r.currentPhase&&console.log(`Current phase: ${r.currentPhase.title} [${r.currentPhase.status}]`),r.currentTask&&console.log(`Current task: ${r.currentTask.title} [${r.currentTask.status}]`),console.log(`Phases: ${r.progress.phasesCompleted}/${r.progress.phasesTotal}`),console.log(`Tasks: ${r.progress.tasksCompleted}/${r.progress.tasksTotal}`),r.nextActions.length&&(console.log(),console.log("Next actions:"),r.nextActions.forEach((e,t)=>console.log(`${t+1}. ${e}`))),r.blockers.length&&(console.log(),console.log("Blockers:"),r.blockers.forEach(e=>console.log(`- ${e}`))),r.lastSession&&(console.log(),console.log(`Last session: ${r.lastSession.agent} / ${r.lastSession.nativeSessionId}`))}async function ct(){let[r="help",...e]=process.argv.slice(2),t=new b().detect(it(e,"--project")??process.cwd()),o=at(t);if(r==="reconcile"){let s=await oe(t,o);se(s);return}if(r==="status"){let s=await M(t,o);if(!s){console.log("No active work state yet.");return}se(s);return}if(r==="json"){let s=await M(t,o);console.log(JSON.stringify(s,null,2));return}console.log(`Work Continuity
7
7
 
8
8
  Commands:
9
9
  status [--project PATH]
10
10
  json [--project PATH]
11
11
  reconcile [--project PATH]
12
- `)}qe().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
12
+ `)}ct().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
package/package.json CHANGED
@@ -1,7 +1,30 @@
1
1
  {
2
2
  "name": "toolnet-memory",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
+ "description": "Persistent project memory, work continuity, and code intelligence for AI coding agents.",
4
5
  "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/LBT-AI/toolnet-memory.git"
10
+ },
11
+ "homepage": "https://github.com/LBT-AI/toolnet-memory#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/LBT-AI/toolnet-memory/issues"
14
+ },
15
+ "engines": {
16
+ "node": ">=22"
17
+ },
18
+ "keywords": [
19
+ "ai",
20
+ "memory",
21
+ "coding-agents",
22
+ "mcp",
23
+ "opencode",
24
+ "codex",
25
+ "antigravity",
26
+ "code-intelligence"
27
+ ],
5
28
  "scripts": {
6
29
  "dev": "tsx src/index.ts",
7
30
  "build": "tsc -p tsconfig.json",