toolnet-memory 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bundle/init.js ADDED
@@ -0,0 +1,1069 @@
1
+ import{existsSync as Ue,statSync as kn}from"node:fs";import{resolve as Rn,join as jn}from"node:path";import{existsSync as Ke,readFileSync as Ve}from"node:fs";import{homedir as We}from"node:os";import{join as Ge}from"node:path";function qe(e){let t=e.trim();return t.length>=2&&t.startsWith('"')&&t.endsWith('"')?(t=t.slice(1,-1),t.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):t.length>=2&&t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Qe(){let e=process.env.TOOLNET_GLOBAL_ENV??Ge(We(),".config","toolnet-memory",".env");if(!Ke(e))return;let t=Ve(e,"utf8");for(let n of t.split(/\r?\n/)){let o=n.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]=qe(o.slice(s+1)))}}Qe();import{createHash as Xe}from"node:crypto";import{existsSync as k,mkdirSync as Ze,readFileSync as et,renameSync as tt,writeFileSync as nt}from"node:fs";import{basename as ot,dirname as w,join as C,parse as V,resolve as h}from"node:path";var W=".toolnet",rt="project.json";function st(e){return Xe("sha256").update(e).digest("hex").slice(0,16)}function R(e){return C(e,W,rt)}function it(e){return k(R(e))}function at(e,t){let n=h(e),o=V(n).root;for(;;){if(it(n))return n;if(n===o||t&&n===h(t))break;let s=w(n);if(s===n)break;n=s}return null}function ct(e){let t=h(e),n=V(t).root,o=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(o.some(i=>k(C(t,i))))return t;if(t===n)break;let s=w(t);if(s===t)break;t=s}return h(e)}function lt(e){let t;try{t=JSON.parse(et(e,"utf8"))}catch(s){throw new Error(`Invalid ToolNet project manifest: ${e}: ${s instanceof Error?s.message:String(s)}`)}if(!t||typeof t!="object")throw new Error(`Invalid ToolNet project manifest: ${e}`);let n=t;if(typeof n.id!="string"||!n.id.trim())throw new Error(`ToolNet project manifest is missing id: ${e}`);if(typeof n.name!="string"||!n.name.trim())throw new Error(`ToolNet project manifest is missing name: ${e}`);let o=new Date().toISOString();return{version:1,id:n.id,name:n.name,remote:typeof n.remote=="string"&&n.remote.trim()?n.remote:n.name,rootPath:typeof n.rootPath=="string"?n.rootPath:w(w(e)),createdAt:typeof n.createdAt=="string"?n.createdAt:o,updatedAt:typeof n.updatedAt=="string"?n.updatedAt:o,graphVersion:typeof n.graphVersion=="number"?n.graphVersion:0,memoryVersion:typeof n.memoryVersion=="number"?n.memoryVersion:0,metadata:n.metadata&&typeof n.metadata=="object"?n.metadata:void 0}}function U(e,t){let n=C(e,W);Ze(n,{recursive:!0});let o=R(e),s=`${o}.tmp-${process.pid}`;nt(s,JSON.stringify(t,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),tt(s,o)}function K(e,t){return{id:e.id,name:e.name,remote:e.remote,rootPath:t,createdAt:e.createdAt,updatedAt:e.updatedAt,graphVersion:e.graphVersion,memoryVersion:e.memoryVersion,metadata:e.metadata}}var O=class{detect(t=process.cwd()){let n=h(t),o=ct(n),i=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(u=>k(C(o,u))),r=at(n,i?o:void 0);if(r){let u=R(r),p=lt(u);return p.rootPath!==r&&(p.rootPath=r,p.updatedAt=new Date().toISOString(),U(r,p)),K(p,r)}let a=new Date().toISOString(),l=ot(o),c={version:1,id:st(o),name:l,remote:l,rootPath:o,createdAt:a,updatedAt:a,graphVersion:0,memoryVersion:0};return U(o,c),K(c,o)}};var G=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],m={clear:"\r\x1B[2K",cyan:"\x1B[36m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",amber:"\x1B[38;5;214m",dim:"\x1B[2m",reset:"\x1B[0m"};function q(e,t=16){let o=Math.max(1,t-4+1),s=e%o;return"\u2500".repeat(s)+"\u2501".repeat(4)+"\u2500".repeat(Math.max(0,t-s-4))}function Q(e){let t=Date.now()-e;return t<1e3?`${t}ms`:t<1e4?`${(t/1e3).toFixed(1)}s`:`${Math.round(t/1e3)}s`}var j=class{stream;enabled;interactive;color;intervalMs;display;label;frame=0;startedAt=0;timer;active=!1;constructor(t,n={}){this.label=t,this.stream=n.stream??process.stderr,this.enabled=n.enabled??!0,this.interactive=n.interactive??this.stream.isTTY===!0,this.color=n.color??(this.interactive&&process.env.NO_COLOR===void 0),this.intervalMs=Math.max(40,n.intervalMs??80),this.display=n.display??"spinner"}start(){return!this.enabled||this.active?this:(this.active=!0,this.startedAt=Date.now(),this.interactive?(this.render(),this.timer=setInterval(()=>{this.frame=(this.frame+1)%1e4,this.render()},this.intervalMs),this.timer.unref?.(),this):(this.stream.write(`\u2192 ${this.label}
4
+ `),this))}update(t){return this.label=t,this.enabled&&this.active&&this.interactive&&this.render(),this}succeed(t){this.finish("\u2713",t??this.label,m.green)}fail(t){this.finish("\u2717",t??this.label,m.red)}warn(t){this.finish("!",t??this.label,m.yellow)}stop(){this.active&&(this.timer&&(clearInterval(this.timer),this.timer=void 0),this.enabled&&this.interactive&&this.stream.write(m.clear),this.active=!1)}render(){if(!this.enabled||!this.active||!this.interactive)return;let t=G[this.frame%G.length],n=this.display==="bar"?this.color?`${m.amber}${q(this.frame)}${m.reset}`:q(this.frame):this.color?`${m.cyan}${t}${m.reset}`:t,o=Q(this.startedAt),s=this.color?`${m.dim}${o}${m.reset}`:o;this.stream.write(`${m.clear}${n} ${this.label} ${s}`)}finish(t,n,o){if(!this.enabled){this.active=!1;return}this.startedAt||(this.startedAt=Date.now()),this.timer&&(clearInterval(this.timer),this.timer=void 0);let s=Q(this.startedAt),i=this.color?`${o}${t}${m.reset}`:t,r=this.color?`${m.dim}${s}${m.reset}`:s;this.interactive?this.stream.write(`${m.clear}${i} ${n} ${r}
5
+ `):this.stream.write(`${i} ${n} (${s})
6
+ `),this.active=!1}};async function N(e,t,n={}){let o=new j(e,n).start();try{let s=await t();return o.succeed(),s}catch(s){throw o.fail(),s}}import{existsSync as mt}from"node:fs";import{homedir as pt}from"node:os";import{join as gt}from"node:path";import{spawnSync as ft}from"node:child_process";import{homedir as ut}from"node:os";import{join as f}from"node:path";function X(e={}){return f(e.home??ut(),".gemini")}function _(e={}){return f(X(e),"config")}function M(e={}){return f(_(e),"mcp_config.json")}function I(e={}){return f(_(e),"hooks.json")}function Z(e={}){return f(X(e),"antigravity-cli")}function ee(e="toolnet-memory",t={}){return f(Z(t),"plugins",e)}function te(e={}){return[Z(e),_(e)]}import{homedir as dt}from"node:os";import{join as v}from"node:path";function g(e={}){let t=e.xdgConfigHome??process.env.XDG_CONFIG_HOME?.trim();return t?v(t,"opencode"):v(e.home??dt(),".config","opencode")}function ne(e={}){return v(g(e),"opencode.json")}function oe(e={}){return v(g(e),"plugins")}function re(e={}){return v(g(e),"AGENTS.md")}import{homedir as se}from"node:os";import{join as P}from"node:path";function F(e={}){return P(e.home??se(),".claude")}function ie(e={}){return P(F(e),"settings.json")}function ae(e={}){return P(e.home??se(),".claude.json")}function yt(e){return ft("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function A(e){let t=e.commandExists(e.command),n=e.configPaths.filter(i=>mt(i)),o=n.length>0,s=[];t&&s.push(`command:${e.command}`);for(let i of n)s.push(`config:${i}`);return{agent:e.agent,detected:t||o,commandDetected:t,configDetected:o,evidence:s}}function ce(e={}){let t=e.home??pt(),n=e.commandExists??yt,o=e.codexHome??process.env.CODEX_HOME??gt(t,".codex");return[A({agent:"agy",command:"agy",commandExists:n,configPaths:te({home:t})}),A({agent:"opencode",command:"opencode",commandExists:n,configPaths:[g({home:t,xdgConfigHome:e.xdgConfigHome})]}),A({agent:"claude",command:"claude",commandExists:n,configPaths:[F({home:t})]}),A({agent:"codex",command:"codex",commandExists:n,configPaths:[o]})]}import{existsSync as T,mkdirSync as ge,readFileSync as fe,renameSync as jt,writeFileSync as Nt}from"node:fs";import{dirname as _t,join as E}from"node:path";import{existsSync as ht,mkdirSync as vt,readFileSync as bt,renameSync as xt,rmSync as St,writeFileSync as wt}from"node:fs";import{dirname as Ot}from"node:path";function Ct(e){return`'${e.replace(/'/g,"'\\''")}'`}function le(e={}){let t=e.hooksFile??I();vt(Ot(t),{recursive:!0,mode:448});let n={};if(ht(t)){let r;try{r=JSON.parse(bt(t,"utf8"))}catch(a){throw new Error(`Invalid existing Agy hooks.json: ${a instanceof Error?a.message:String(a)}`)}if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("Invalid existing Agy hooks.json: root must be a JSON object.");n=r}let o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",s=`${Ct(o)} session:agy-hook`;n["toolnet-memory"]={enabled:!0,PreToolUse:[{matcher:"view_file|list_dir|find_by_name|grep_search|run_command",hooks:[{type:"command",command:`${s} pre-tool`,timeout:5}]}],PreInvocation:[{type:"command",command:`${s} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${s} post`,timeout:15}],Stop:[{type:"command",command:`${s} stop`,timeout:30}]};let i=`${t}.tmp-${process.pid}-${Date.now()}`;try{wt(i,JSON.stringify(n,null,2)+`
7
+ `,{encoding:"utf8",mode:384}),xt(i,t)}finally{St(i,{force:!0})}return t}import{existsSync as Mt,mkdirSync as It,readFileSync as At,renameSync as Et,writeFileSync as Tt}from"node:fs";import{dirname as kt}from"node:path";function b(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Rt(e,t){It(kt(e),{recursive:!0,mode:448});let n=`${e}.tmp-${process.pid}-${Date.now()}`;Tt(n,`${JSON.stringify(t,null,2)}
8
+ `,{encoding:"utf8",mode:384}),Et(n,e)}function ue(e){if(!Mt(e))return{};let t=At(e,"utf8").trim();if(!t)return{};let n;try{n=JSON.parse(t)}catch(o){throw new Error(`Invalid existing Agy MCP config: ${o instanceof Error?o.message:String(o)}`)}if(!b(n))throw new Error("Invalid existing Agy MCP config: root must be a JSON object.");return n}function de(e,t){return b(e)?e.command===t&&Array.isArray(e.args)&&e.args.length===1&&e.args[0]==="mcp":!1}function me(e={}){let t=e.configFile??M(),n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",s=ue(t),i=s.mcpServers;if(i!==void 0&&!b(i))throw new Error("Invalid existing Agy MCP config: mcpServers must be an object.");let r=b(i)?{...i}:{},a=r[o];if(de(a,n))return{installed:!0,changed:!1,configFile:t,serverName:o,command:n,args:["mcp"]};r[o]={command:n,args:["mcp"]};let l={...s,mcpServers:r};Rt(t,l);let u=ue(t).mcpServers;if(!b(u)||!de(u[o],n))throw new Error("Agy MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:t,serverName:o,command:n,args:["mcp"]}}var Pt=`# ToolNet Memory Continuity
9
+
10
+ ToolNet Memory is the authoritative continuity layer for previous project work.
11
+
12
+ ## Resume / continue behavior
13
+
14
+ Whenever the user asks to continue, resume, finish, pick up, return to, or complete previous work:
15
+
16
+ 1. FIRST call the ToolNet Memory MCP tool \`memory_agent_ask\`.
17
+ 2. Use ToolNet's compact continuity result to determine:
18
+ - current task
19
+ - completed work
20
+ - current or last file
21
+ - TODOs
22
+ - blockers
23
+ - next action
24
+ 3. Only AFTER continuity is known may you inspect current source or git to verify repository truth.
25
+
26
+ ## Forbidden continuity recovery
27
+
28
+ Do NOT reconstruct previous work by reading, listing, searching, or shelling into:
29
+
30
+ - \`.toolnet/sessions/**\`
31
+ - \`state.json\`
32
+ - \`events.jsonl\`
33
+ - raw transcripts
34
+ - \`~/.gemini/antigravity-cli/brain/**\`
35
+ - Antigravity \`transcript.jsonl\`
36
+ - another coding agent's internal session history
37
+
38
+ Do NOT run Bash/cat/tail/grep against those locations to discover previous work.
39
+
40
+ Do NOT search the filesystem for the implementation or schema of \`memory_agent_ask\`.
41
+ Invoke the MCP tool directly.
42
+
43
+ For direct continuity facts, prefer \`mode="local"\`.
44
+ Use \`mode="ai"\` only when continuity is ambiguous or requires synthesis.
45
+
46
+ Current repository evidence overrides stale memory after ToolNet has restored the working context.
47
+
48
+ Do not ask the user to repeat context already available through ToolNet Memory.
49
+ `;function ye(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e,t){ge(_t(e),{recursive:!0});let n=`${e}.tmp-${process.pid}-${Date.now()}`;Nt(n,t,{encoding:"utf8",mode:384}),jt(n,e)}function pe(e,t){T(e)&&fe(e,"utf8")===t||D(e,t)}function he(e){if(!T(e))return{};let t=fe(e,"utf8").trim();if(!t)return{};let n;try{n=JSON.parse(t)}catch(o){throw new Error(`Invalid legacy Antigravity config ${e}: ${o instanceof Error?o.message:String(o)}`)}if(!ye(n))throw new Error(`Invalid legacy Antigravity config ${e}: root must be object`);return n}function Ft(e,t){if(!T(e))return!1;let n=he(e);if(!ye(n.mcpServers)||!Object.prototype.hasOwnProperty.call(n.mcpServers,t))return!1;let o={...n.mcpServers};return delete o[t],D(e,`${JSON.stringify({...n,mcpServers:o},null,2)}
50
+ `),!0}function Dt(e){if(!T(e))return!1;let t=he(e);if(!Object.prototype.hasOwnProperty.call(t,"toolnet-memory"))return!1;let n={...t};return delete n["toolnet-memory"],D(e,`${JSON.stringify(n,null,2)}
51
+ `),!0}function ve(e={}){let t=e.pluginName??"toolnet-memory",n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.pluginRoot??ee(t),s=E(o,"plugin.json"),i=E(o,"mcp_config.json"),r=E(o,"hooks.json"),a=E(o,"rules","toolnet-memory-continuity.md");ge(o,{recursive:!0,mode:448}),pe(s,`${JSON.stringify({$schema:"https://antigravity.google/schemas/v1/plugin.json",name:t,description:"Persistent project continuity and memory for Antigravity coding sessions."},null,2)}
52
+ `),me({configFile:i,binary:n,serverName:"toolnet-memory"}),le({hooksFile:r,binary:n}),pe(a,`${Pt.trim()}
53
+ `);let l=e.legacyMcpFile??M(),c=e.legacyHooksFile??I(),u=[];return l!==i&&Ft(l,"toolnet-memory")&&u.push(l),c!==r&&Dt(c)&&u.push(c),{installed:!0,pluginRoot:o,files:[s,i,r,a],migratedLegacy:u}}import{existsSync as Lt,mkdirSync as Se,readFileSync as Jt,writeFileSync as we}from"node:fs";import{join as Bt}from"node:path";var $t="memory_agent_ask";function be(){return`
54
+ [TOOLNET MEMORY AGENT]
55
+
56
+ Tool available:
57
+ - ${$t}
58
+
59
+ Use it automatically BEFORE guessing when:
60
+
61
+ - The user asks to continue, resume, or pick up previous work.
62
+ - The user says things like:
63
+ - "ti\u1EBFp t\u1EE5c task l\xFAc n\xE3y"
64
+ - "l\xE0m ti\u1EBFp ph\u1EA7n \u0111ang d\u1EDF"
65
+ - "agent tr\u01B0\u1EDBc \u0111ang l\xE0m g\xEC?"
66
+ - "d\u1EEBng \u1EDF \u0111\xE2u?"
67
+ - "todo n\xE0o ch\u01B0a xong?"
68
+ - "continue the previous task"
69
+ - "resume the last session"
70
+ - Previous-agent state, unfinished work, blockers,
71
+ decisions, touched files, or next actions are unclear.
72
+ - Fast startup context is not enough to safely continue.
73
+
74
+ Preferred mode:
75
+
76
+ - mode="local"
77
+ for direct factual questions such as:
78
+ current task, last file, blocker, completed TODOs.
79
+
80
+ - mode="ai"
81
+ for composite or ambiguous continuity questions
82
+ that benefit from ToolNet Memory Agent reasoning.
83
+
84
+ Do NOT call it automatically when:
85
+
86
+ - Normal startup context already gives enough information.
87
+ - The question is unrelated to previous project work.
88
+ - The answer is obvious from current repository evidence.
89
+
90
+ Rules:
91
+
92
+ - Never invent previous work.
93
+ - Current repository evidence overrides stale memory.
94
+ - NEVER reconstruct previous work by reading ToolNet internal session files.
95
+ - NEVER read/list/search .toolnet/sessions/**, session state.json,
96
+ events.jsonl, or raw transcripts to discover previous-agent state.
97
+ - Do not search the filesystem for the implementation/schema of
98
+ memory_agent_ask. Invoke the MCP tool directly when deeper
99
+ continuity is required.
100
+ - Do not dump raw transcripts or full memory.
101
+ - After receiving the ToolNet answer, continue the task
102
+ instead of asking the user to repeat known context.
103
+ - If ToolNet says information is not recorded, say so.
104
+ `.trim()}var xe="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",$="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function Ht(){let e=re();Se(g(),{recursive:!0});let t=`${xe}
105
+ ## ToolNet Memory
106
+
107
+ ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
108
+
109
+ Rules:
110
+
111
+ 1. Do not create a ToolNet project automatically.
112
+ 2. If no valid \`.toolnet/project.json\` exists in the current project or an ancestor, ignore ToolNet Memory.
113
+ 3. Normal startup context must stay small and selective.
114
+ 4. Do not automatically run deep recovery commands.
115
+ 5. Use ToolNet MCP or selective retrieval when older project knowledge is actually needed.
116
+ 6. Raw transcripts must not be injected into prompts.
117
+ 7. Current repository evidence has priority over stale memory when they conflict.
118
+
119
+
120
+ ${be()}
121
+
122
+ ${$}`,n=Lt(e)?Jt(e,"utf8"):"",o=n.indexOf(xe),s=n.indexOf($);return o>=0&&s>=o?n=n.slice(0,o)+t+n.slice(s+$.length):(n=n.trimEnd(),n&&(n+=`
123
+
124
+ `),n+=t),we(e,n.trimEnd()+`
125
+ `,{encoding:"utf8",mode:384}),e}function Oe(e={}){let t=e.directory??oe();Se(t,{recursive:!0}),Ht();let n=Bt(t,"toolnet-memory.js"),o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",s=`
126
+ // Generated by ToolNet Memory.
127
+ // OpenCode integration:
128
+ // - project gated
129
+ // - selective context injection
130
+ // - incremental session checkpoints
131
+ // - periodic durability sync
132
+ // - best-effort shutdown flush
133
+
134
+ import fs from "node:fs"
135
+ import path from "node:path"
136
+ import os from "node:os"
137
+
138
+ const TOOLNET_BINARY =
139
+ ${JSON.stringify(o)}
140
+
141
+ const CONTEXT_MAX_TOKENS = 700
142
+
143
+ const CONTEXT_CACHE_MS = 5000
144
+
145
+ const LOCAL_CAPTURE_MS = 15000
146
+
147
+ const REMOTE_SYNC_MS = 60000
148
+
149
+ const EVENT_CAPTURE_DEBOUNCE_MS = 1200
150
+
151
+ const CAPTURE_TIMEOUT_MS = 20000
152
+
153
+ const REMOTE_TIMEOUT_MS = 120000
154
+
155
+ const GLOBAL_STATUS_FILE = path.join(
156
+ os.homedir(),
157
+ ".config",
158
+ "toolnet-memory",
159
+ "opencode-sync-status.json"
160
+ )
161
+
162
+ function projectStatusFile(
163
+ data
164
+ ) {
165
+ if (
166
+ typeof data?.projectRoot ===
167
+ "string" &&
168
+ data.projectRoot.length > 0
169
+ ) {
170
+ return path.join(
171
+ data.projectRoot,
172
+ ".toolnet",
173
+ "runtime",
174
+ "opencode-status.json"
175
+ )
176
+ }
177
+
178
+ return GLOBAL_STATUS_FILE
179
+ }
180
+
181
+ function writeStatus(data) {
182
+ try {
183
+ const statusFile =
184
+ projectStatusFile(
185
+ data
186
+ )
187
+
188
+ fs.mkdirSync(
189
+ path.dirname(
190
+ statusFile
191
+ ),
192
+ {
193
+ recursive: true,
194
+ }
195
+ )
196
+
197
+ fs.writeFileSync(
198
+ statusFile,
199
+ JSON.stringify(
200
+ {
201
+ timestamp:
202
+ new Date()
203
+ .toISOString(),
204
+ ...data,
205
+ },
206
+ null,
207
+ 2
208
+ ) + "\\n"
209
+ )
210
+ } catch {
211
+ // Status reporting must
212
+ // never break OpenCode.
213
+ }
214
+ }
215
+
216
+ function validProjectManifest(
217
+ file
218
+ ) {
219
+ try {
220
+ const parsed =
221
+ JSON.parse(
222
+ fs.readFileSync(
223
+ file,
224
+ "utf8"
225
+ )
226
+ )
227
+
228
+ return Boolean(
229
+ parsed &&
230
+ typeof parsed ===
231
+ "object" &&
232
+ typeof parsed.id ===
233
+ "string" &&
234
+ parsed.id.length > 0
235
+ )
236
+ } catch {
237
+ return false
238
+ }
239
+ }
240
+
241
+ function findProjectRoot(
242
+ inputDirectory
243
+ ) {
244
+ let current =
245
+ path.resolve(
246
+ inputDirectory
247
+ )
248
+
249
+ const filesystemRoot =
250
+ path.parse(
251
+ current
252
+ ).root
253
+
254
+ while (true) {
255
+ const manifest =
256
+ path.join(
257
+ current,
258
+ ".toolnet",
259
+ "project.json"
260
+ )
261
+
262
+ if (
263
+ fs.existsSync(
264
+ manifest
265
+ ) &&
266
+ validProjectManifest(
267
+ manifest
268
+ )
269
+ ) {
270
+ return current
271
+ }
272
+
273
+ if (
274
+ current ===
275
+ filesystemRoot
276
+ ) {
277
+ break
278
+ }
279
+
280
+ const parent =
281
+ path.dirname(
282
+ current
283
+ )
284
+
285
+ if (
286
+ parent === current
287
+ ) {
288
+ break
289
+ }
290
+
291
+ current = parent
292
+ }
293
+
294
+ return null
295
+ }
296
+
297
+ function getSessionId(
298
+ event
299
+ ) {
300
+ const p =
301
+ event?.properties ?? {}
302
+
303
+ const candidates = [
304
+ p.sessionID,
305
+ p.sessionId,
306
+ p.info?.id,
307
+ p.session?.id,
308
+ ]
309
+
310
+ return (
311
+ candidates.find(
312
+ value =>
313
+ typeof value ===
314
+ "string" &&
315
+ value.length > 0
316
+ ) ?? null
317
+ )
318
+ }
319
+
320
+ async function runWithTimeout(
321
+ args,
322
+ {
323
+ stdout = "ignore",
324
+ timeout =
325
+ REMOTE_TIMEOUT_MS,
326
+ } = {}
327
+ ) {
328
+ const child =
329
+ Bun.spawn(
330
+ args,
331
+ {
332
+ stdin:
333
+ "ignore",
334
+ stdout,
335
+ stderr:
336
+ "ignore",
337
+ }
338
+ )
339
+
340
+ let timer
341
+
342
+ const timeoutPromise =
343
+ new Promise(
344
+ (_, reject) => {
345
+ timer =
346
+ setTimeout(
347
+ () => {
348
+ try {
349
+ child.kill()
350
+ } catch {}
351
+
352
+ reject(
353
+ new Error(
354
+ "ToolNet command timeout"
355
+ )
356
+ )
357
+ },
358
+ timeout
359
+ )
360
+ }
361
+ )
362
+
363
+ try {
364
+ const exitCode =
365
+ await Promise.race([
366
+ child.exited,
367
+ timeoutPromise,
368
+ ])
369
+
370
+ if (
371
+ typeof exitCode ===
372
+ "number" &&
373
+ exitCode !== 0
374
+ ) {
375
+ throw new Error(
376
+ \`ToolNet exited with code \${exitCode}\`
377
+ )
378
+ }
379
+
380
+ return child
381
+ } finally {
382
+ if (timer) {
383
+ clearTimeout(
384
+ timer
385
+ )
386
+ }
387
+ }
388
+ }
389
+
390
+ export const ToolNetMemoryPlugin =
391
+ async ({
392
+ directory,
393
+ }) => {
394
+ /*
395
+ * CRITICAL:
396
+ * Never auto-create a ToolNet
397
+ * project just because OpenCode
398
+ * happens to start in /root,
399
+ * /tmp, $HOME, etc.
400
+ */
401
+ const projectRoot =
402
+ findProjectRoot(
403
+ directory
404
+ )
405
+
406
+ if (!projectRoot) {
407
+ writeStatus({
408
+ active: false,
409
+ reason:
410
+ "no-toolnet-project",
411
+ directory,
412
+ })
413
+
414
+ return {}
415
+ }
416
+
417
+ writeStatus({
418
+ active: true,
419
+ projectRoot,
420
+ state:
421
+ "plugin-loaded",
422
+ })
423
+
424
+ let lastSessionId =
425
+ null
426
+
427
+ /*
428
+ * Two independent lanes:
429
+ *
430
+ * captureChain:
431
+ * OpenCode DB -> local fsync WAL -> current work
432
+ *
433
+ * remoteInFlight:
434
+ * WAL -> Hugging Face / S3
435
+ *
436
+ * A slow remote backend must never block local capture.
437
+ */
438
+ let captureChain =
439
+ Promise.resolve()
440
+
441
+ let remoteInFlight =
442
+ null
443
+
444
+ let debounceTimer =
445
+ null
446
+
447
+ let contextCache = {
448
+ value: "",
449
+ expiresAt: 0,
450
+ }
451
+
452
+ /*
453
+ * Compact startup context is injected once
454
+ * per native OpenCode session.
455
+ *
456
+ * Deep history is retrieved through
457
+ * memory_agent_ask / MCP on demand.
458
+ */
459
+ const injectedSessions =
460
+ new Set()
461
+
462
+ function contextSessionKey(
463
+ input
464
+ ) {
465
+ const candidates = [
466
+ input?.sessionID,
467
+ input?.sessionId,
468
+ input?.session?.id,
469
+ input?.info?.id,
470
+ lastSessionId,
471
+ ]
472
+
473
+ const sessionId =
474
+ candidates.find(
475
+ value =>
476
+ typeof value ===
477
+ "string" &&
478
+ value.length > 0
479
+ )
480
+
481
+ return sessionId
482
+ ? "session:" +
483
+ sessionId
484
+ : "project:" +
485
+ projectRoot
486
+ }
487
+
488
+ async function readContext() {
489
+ const now =
490
+ Date.now()
491
+
492
+ if (
493
+ contextCache.value &&
494
+ now <
495
+ contextCache.expiresAt
496
+ ) {
497
+ return (
498
+ contextCache.value
499
+ )
500
+ }
501
+
502
+ try {
503
+ const child =
504
+ Bun.spawn(
505
+ [
506
+ TOOLNET_BINARY,
507
+ "context:print",
508
+ "--project",
509
+ projectRoot,
510
+ "--tokens",
511
+ String(
512
+ CONTEXT_MAX_TOKENS
513
+ ),
514
+ ],
515
+ {
516
+ stdin:
517
+ "ignore",
518
+ stdout:
519
+ "pipe",
520
+ stderr:
521
+ "ignore",
522
+ }
523
+ )
524
+
525
+ const textPromise =
526
+ new Response(
527
+ child.stdout
528
+ ).text()
529
+
530
+ const exitCode =
531
+ await child.exited
532
+
533
+ if (
534
+ exitCode !== 0
535
+ ) {
536
+ return ""
537
+ }
538
+
539
+ const text =
540
+ (
541
+ await textPromise
542
+ ).trim()
543
+
544
+ contextCache = {
545
+ value: text,
546
+ expiresAt:
547
+ now +
548
+ CONTEXT_CACHE_MS,
549
+ }
550
+
551
+ return text
552
+ } catch {
553
+ return ""
554
+ }
555
+ }
556
+
557
+ async function syncNow(
558
+ sessionId,
559
+ {
560
+ flag = null,
561
+ reason = "unknown",
562
+ localOnly = false,
563
+ timeout =
564
+ REMOTE_TIMEOUT_MS,
565
+ } = {}
566
+ ) {
567
+ if (!sessionId) {
568
+ return
569
+ }
570
+
571
+ const args = [
572
+ TOOLNET_BINARY,
573
+ "session:opencode-sync",
574
+ sessionId,
575
+ "--project",
576
+ projectRoot,
577
+ ]
578
+
579
+ if (flag) {
580
+ args.push(
581
+ flag
582
+ )
583
+ }
584
+
585
+ if (localOnly) {
586
+ args.push(
587
+ "--local-only"
588
+ )
589
+ }
590
+
591
+ try {
592
+ await runWithTimeout(
593
+ args,
594
+ {
595
+ timeout,
596
+ }
597
+ )
598
+
599
+ writeStatus({
600
+ active: true,
601
+ projectRoot,
602
+ sessionId,
603
+ reason,
604
+ mode:
605
+ localOnly
606
+ ? "local"
607
+ : "remote",
608
+ state:
609
+ localOnly
610
+ ? "capture-success"
611
+ : "sync-success",
612
+ })
613
+ } catch (error) {
614
+ writeStatus({
615
+ active: true,
616
+ projectRoot,
617
+ sessionId,
618
+ reason,
619
+ mode:
620
+ localOnly
621
+ ? "local"
622
+ : "remote",
623
+ state:
624
+ localOnly
625
+ ? "capture-failed"
626
+ : "sync-failed",
627
+ error:
628
+ error instanceof
629
+ Error
630
+ ? error.message
631
+ : String(
632
+ error
633
+ ),
634
+ })
635
+
636
+ throw error
637
+ }
638
+ }
639
+
640
+ function queueCapture(
641
+ sessionId,
642
+ flag,
643
+ reason
644
+ ) {
645
+ if (!sessionId) {
646
+ return captureChain
647
+ }
648
+
649
+ lastSessionId =
650
+ sessionId
651
+
652
+ /*
653
+ * Local captures are serialized with each other,
654
+ * but completely independent from remote sync.
655
+ */
656
+ captureChain =
657
+ captureChain
658
+ .catch(
659
+ () =>
660
+ undefined
661
+ )
662
+ .then(
663
+ () =>
664
+ syncNow(
665
+ sessionId,
666
+ {
667
+ flag,
668
+ reason,
669
+ localOnly: true,
670
+ timeout:
671
+ CAPTURE_TIMEOUT_MS,
672
+ }
673
+ )
674
+ )
675
+ .catch(
676
+ () =>
677
+ undefined
678
+ )
679
+
680
+ return captureChain
681
+ }
682
+
683
+ function queueRemote(
684
+ sessionId,
685
+ flag,
686
+ reason
687
+ ) {
688
+ if (!sessionId) {
689
+ return Promise.resolve()
690
+ }
691
+
692
+ lastSessionId =
693
+ sessionId
694
+
695
+ /*
696
+ * Never build an endless remote backlog.
697
+ * One remote flush is enough because WAL keeps
698
+ * every pending local event until acknowledged.
699
+ */
700
+ if (remoteInFlight) {
701
+ return remoteInFlight
702
+ }
703
+
704
+ remoteInFlight =
705
+ syncNow(
706
+ sessionId,
707
+ {
708
+ flag,
709
+ reason,
710
+ localOnly: false,
711
+ timeout:
712
+ REMOTE_TIMEOUT_MS,
713
+ }
714
+ )
715
+ .catch(
716
+ () =>
717
+ undefined
718
+ )
719
+ .finally(
720
+ () => {
721
+ remoteInFlight =
722
+ null
723
+ }
724
+ )
725
+
726
+ return remoteInFlight
727
+ }
728
+
729
+ function scheduleCapture(
730
+ sessionId,
731
+ reason
732
+ ) {
733
+ if (!sessionId) {
734
+ return
735
+ }
736
+
737
+ if (debounceTimer) {
738
+ clearTimeout(
739
+ debounceTimer
740
+ )
741
+ }
742
+
743
+ debounceTimer =
744
+ setTimeout(
745
+ () => {
746
+ debounceTimer =
747
+ null
748
+
749
+ void queueCapture(
750
+ sessionId,
751
+ null,
752
+ reason
753
+ )
754
+ },
755
+ EVENT_CAPTURE_DEBOUNCE_MS
756
+ )
757
+
758
+ if (
759
+ typeof debounceTimer.unref ===
760
+ "function"
761
+ ) {
762
+ debounceTimer.unref()
763
+ }
764
+ }
765
+
766
+ /*
767
+ * Crash-safe LOCAL checkpoint.
768
+ *
769
+ * Fast lane:
770
+ * OpenCode DB -> fsync WAL -> current work.
771
+ *
772
+ * No network dependency.
773
+ */
774
+ const localPeriodic =
775
+ setInterval(
776
+ () => {
777
+ if (
778
+ lastSessionId
779
+ ) {
780
+ void queueCapture(
781
+ lastSessionId,
782
+ null,
783
+ "periodic-local"
784
+ )
785
+ }
786
+ },
787
+ LOCAL_CAPTURE_MS
788
+ )
789
+
790
+ /*
791
+ * Remote durability is deliberately slower
792
+ * and independent from the local lane.
793
+ */
794
+ const remotePeriodic =
795
+ setInterval(
796
+ () => {
797
+ if (
798
+ lastSessionId
799
+ ) {
800
+ void queueRemote(
801
+ lastSessionId,
802
+ null,
803
+ "periodic-remote"
804
+ )
805
+ }
806
+ },
807
+ REMOTE_SYNC_MS
808
+ )
809
+
810
+ for (
811
+ const timer of [
812
+ localPeriodic,
813
+ remotePeriodic,
814
+ ]
815
+ ) {
816
+ if (
817
+ typeof timer.unref ===
818
+ "function"
819
+ ) {
820
+ timer.unref()
821
+ }
822
+ }
823
+
824
+ return {
825
+ event: async ({
826
+ event,
827
+ }) => {
828
+ const sessionId =
829
+ getSessionId(
830
+ event
831
+ )
832
+
833
+ if (sessionId) {
834
+ lastSessionId =
835
+ sessionId
836
+ }
837
+
838
+ const terminalEvent =
839
+ event.type ===
840
+ "session.idle" ||
841
+ event.type ===
842
+ "session.compacted" ||
843
+ event.type ===
844
+ "session.error"
845
+
846
+ if (
847
+ sessionId &&
848
+ !terminalEvent
849
+ ) {
850
+ scheduleCapture(
851
+ sessionId,
852
+ "event:" +
853
+ event.type
854
+ )
855
+ }
856
+
857
+ if (
858
+ event.type ===
859
+ "session.idle"
860
+ ) {
861
+ await queueCapture(
862
+ sessionId,
863
+ "--idle",
864
+ "session.idle:capture"
865
+ )
866
+
867
+ void queueRemote(
868
+ sessionId,
869
+ null,
870
+ "session.idle:remote"
871
+ )
872
+
873
+ return
874
+ }
875
+
876
+ if (
877
+ event.type ===
878
+ "session.compacted"
879
+ ) {
880
+ await queueCapture(
881
+ sessionId,
882
+ "--compacted",
883
+ "session.compacted:capture"
884
+ )
885
+
886
+ void queueRemote(
887
+ sessionId,
888
+ null,
889
+ "session.compacted:remote"
890
+ )
891
+
892
+ contextCache = {
893
+ value: "",
894
+ expiresAt: 0,
895
+ }
896
+
897
+ return
898
+ }
899
+
900
+ if (
901
+ event.type ===
902
+ "session.error"
903
+ ) {
904
+ await queueCapture(
905
+ sessionId,
906
+ "--error",
907
+ "session.error:capture"
908
+ )
909
+
910
+ void queueRemote(
911
+ sessionId,
912
+ null,
913
+ "session.error:remote"
914
+ )
915
+
916
+ return
917
+ }
918
+ },
919
+
920
+ /*
921
+ * VERIFIED on OpenCode 1.18.14:
922
+ * this hook fires on real model turns.
923
+ *
924
+ * It remains experimental, therefore
925
+ * AGENTS.md + MCP stay as fallbacks.
926
+ */
927
+ "experimental.chat.system.transform":
928
+ async (
929
+ input,
930
+ output
931
+ ) => {
932
+ const injectionKey =
933
+ contextSessionKey(
934
+ input
935
+ )
936
+
937
+ /*
938
+ * Never inject ToolNet context on
939
+ * every model turn.
940
+ */
941
+ if (
942
+ injectedSessions.has(
943
+ injectionKey
944
+ )
945
+ ) {
946
+ return
947
+ }
948
+
949
+ const context =
950
+ await readContext()
951
+
952
+ if (!context) {
953
+ return
954
+ }
955
+
956
+ if (
957
+ Array.isArray(
958
+ output?.system
959
+ )
960
+ ) {
961
+ output.system.push(
962
+ context
963
+ )
964
+
965
+ injectedSessions.add(
966
+ injectionKey
967
+ )
968
+
969
+ return
970
+ }
971
+
972
+ if (
973
+ typeof output?.system ===
974
+ "string"
975
+ ) {
976
+ output.system =
977
+ output.system
978
+ ? output.system +
979
+ "\\n\\n" +
980
+ context
981
+ : context
982
+
983
+ injectedSessions.add(
984
+ injectionKey
985
+ )
986
+ }
987
+ },
988
+
989
+ /*
990
+ * Optional compaction survival hook.
991
+ * We keep it as a supplementary path,
992
+ * never as the only continuity path.
993
+ */
994
+ "experimental.session.compacting":
995
+ async (
996
+ _input,
997
+ output
998
+ ) => {
999
+ const context =
1000
+ await readContext()
1001
+
1002
+ if (
1003
+ context &&
1004
+ Array.isArray(
1005
+ output?.context
1006
+ )
1007
+ ) {
1008
+ output.context.push(
1009
+ context
1010
+ )
1011
+ }
1012
+ },
1013
+
1014
+ /*
1015
+ * VERIFIED for clean OpenCode exits.
1016
+ * Best effort only: kill -9 / crash /
1017
+ * machine loss cannot guarantee this.
1018
+ */
1019
+ dispose: async () => {
1020
+ clearInterval(
1021
+ localPeriodic
1022
+ )
1023
+
1024
+ clearInterval(
1025
+ remotePeriodic
1026
+ )
1027
+
1028
+ if (debounceTimer) {
1029
+ clearTimeout(
1030
+ debounceTimer
1031
+ )
1032
+ }
1033
+
1034
+ /*
1035
+ * On a clean exit we only REQUIRE the
1036
+ * fast local fsync checkpoint.
1037
+ *
1038
+ * Never hold OpenCode shutdown hostage
1039
+ * to a slow remote backend.
1040
+ */
1041
+ if (
1042
+ lastSessionId
1043
+ ) {
1044
+ await queueCapture(
1045
+ lastSessionId,
1046
+ "--idle",
1047
+ "dispose:capture"
1048
+ )
1049
+ }
1050
+
1051
+ try {
1052
+ await captureChain
1053
+ } catch {
1054
+ // Fail open.
1055
+ }
1056
+ },
1057
+ }
1058
+ }
1059
+ `;return we(n,s.trimStart(),{encoding:"utf8",mode:384}),n}import{existsSync as Ie,mkdirSync as Yt,readFileSync as zt,renameSync as Ut,writeFileSync as Kt}from"node:fs";import{dirname as Ae,join as Vt}from"node:path";function y(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Wt(e,t){Yt(Ae(e),{recursive:!0});let n=`${e}.tmp-${process.pid}-${Date.now()}`;Kt(n,`${JSON.stringify(t,null,2)}
1060
+ `,{encoding:"utf8",mode:384}),Ut(n,e)}function Ce(e){if(!Ie(e))return{};let t=zt(e,"utf8").trim();if(!t)return{};let n;try{n=JSON.parse(t)}catch(o){throw new Error(`Invalid existing OpenCode opencode.json: ${o instanceof Error?o.message:String(o)}`)}if(!y(n))throw new Error("Invalid existing OpenCode opencode.json: root must be a JSON object.");return n}function Me(e,t){if(!y(e))return!1;let n=e.command;return e.type==="local"&&e.enabled!==!1&&Array.isArray(n)&&n.length===2&&n[0]===t&&n[1]==="mcp"}function Gt(e,t){let n=e.mcpServers;if(!y(n)||!Object.prototype.hasOwnProperty.call(n,t))return{root:e,changed:!1};let o={...n};return delete o[t],{root:{...e,mcpServers:o},changed:!0}}function Ee(e={}){let t=e.configFile??ne(),n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",s=Vt(Ae(t),"opencode.jsonc"),i=Ie(s)?s:void 0,r=Ce(t),a=Gt(r,o),l=a.root,c=l.mcp;if(c!==void 0&&!y(c))throw new Error("Invalid existing OpenCode config: mcp must be an object.");let u=y(c)?{...c}:{},p=u[o];if(Me(p,n)&&!a.changed)return{installed:!0,changed:!1,configFile:t,serverName:o,command:[n,"mcp"],preservedJsonc:i};u[o]={type:"local",command:[n,"mcp"],enabled:!0};let d={...l,mcp:u};Wt(t,d);let z=Ce(t);if(!y(z.mcp)||!Me(z.mcp[o],n))throw new Error("OpenCode MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:t,serverName:o,command:[n,"mcp"],preservedJsonc:i}}import{existsSync as qt,mkdirSync as Te,readFileSync as Qt,writeFileSync as ke}from"node:fs";import{homedir as Re}from"node:os";import{dirname as je,join as L}from"node:path";function Xt(e){let t=[],n=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,o;for(;o=n.exec(e);){let s=o[1]??o[2]??"";try{t.push(o[1]!==void 0?JSON.parse(`"${s}"`):s)}catch{t.push(s)}}return t}function Ne(e={}){let t=e.configFile??L(process.env.CODEX_HOME??L(Re(),".codex"),"config.toml"),n=e.previousFile??L(Re(),".config","toolnet-memory","codex-notify-previous.json");Te(je(t),{recursive:!0}),Te(je(n),{recursive:!0});let o=qt(t)?Qt(t,"utf8"):"",s=e.binary??"toolnet-memory",i=`notify = [${JSON.stringify(s)}, "session:codex-notify"]`,r=o.split(`
1061
+ `),a=r.findIndex(d=>/^\s*\[/.test(d));a<0&&(a=r.length);let l=-1,c=-1;for(let d=0;d<a;d+=1)if(/^\s*notify\s*=/.test(r[d])){if(l=d,c=d,r[d].includes("[")&&!r[d].includes("]"))for(;c+1<a&&(c+=1,!r[c].includes("]")););break}let u=[];if(l>=0){let d=r.slice(l,c+1).join(`
1062
+ `);u=Xt(d),r.splice(l,c-l+1,i)}else a=r.findIndex(d=>/^\s*\[/.test(d)),a<0&&(a=r.length),r.splice(a,0,i);let p=u.length>=2&&u[u.length-1]==="session:codex-notify";return u.length>0&&!p&&ke(n,JSON.stringify(u,null,2)+`
1063
+ `,{encoding:"utf8",mode:384}),o=r.join(`
1064
+ `),o.endsWith(`
1065
+ `)||(o+=`
1066
+ `),ke(t,o,{encoding:"utf8",mode:384}),{configFile:t,previousFile:n,preservedPrevious:u.length>0&&!p}}import{existsSync as Zt,mkdirSync as en,readFileSync as tn,writeFileSync as nn}from"node:fs";import{homedir as on}from"node:os";import{dirname as rn,join as _e}from"node:path";function sn(e){return`'${e.replace(/'/g,"'\\''")}'`}function Pe(e={}){let t=e.hooksFile??_e(process.env.CODEX_HOME??_e(on(),".codex"),"hooks.json");en(rn(t),{recursive:!0});let n={};if(Zt(t))try{n=JSON.parse(tn(t,"utf8"))}catch(a){throw new Error(`Invalid existing Codex hooks.json: ${a instanceof Error?a.message:String(a)}`)}let o=n.hooks&&typeof n.hooks=="object"&&!Array.isArray(n.hooks)?n.hooks:{};n.hooks=o;let i=(Array.isArray(o.SessionStart)?o.SessionStart:[]).filter(a=>{try{return!JSON.stringify(a).includes("session:codex-context")}catch{return!0}}),r=e.binary??"toolnet-memory";return i.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${sn(r)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),o.SessionStart=i,nn(t,JSON.stringify(n,null,2)+`
1067
+ `,{encoding:"utf8",mode:384}),t}import{spawnSync as an}from"node:child_process";function J(e,t){return an(e,t,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function Fe(e,t){let n=J(e,["mcp","get",t,"--json"]);if(n.status!==0||!n.stdout)return null;try{return JSON.parse(n.stdout)}catch{return null}}function De(e,t){return e.enabled!==!1&&e.transport?.type==="stdio"&&e.transport?.command===t&&Array.isArray(e.transport?.args)&&e.transport?.args.length===1&&e.transport.args[0]==="mcp"}function $e(e={}){let t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=e.codexBinary??"codex",o=e.serverName??"toolnet-memory",s=Fe(n,o);if(s&&De(s,t))return{installed:!0,changed:!1,serverName:o,command:t,args:["mcp"]};if(s){let a=J(n,["mcp","remove",o]);if(a.status!==0)return{installed:!1,changed:!1,serverName:o,command:t,args:["mcp"],error:(a.stderr||a.stdout||"Unable to remove old ToolNet MCP configuration.").trim()}}let i=J(n,["mcp","add",o,"--",t,"mcp"]);if(i.status!==0)return{installed:!1,changed:!1,serverName:o,command:t,args:["mcp"],error:(i.stderr||i.stdout||"Unable to register ToolNet MCP.").trim()};let r=Fe(n,o);return!r||!De(r,t)?{installed:!1,changed:!0,serverName:o,command:t,args:["mcp"],error:"Codex accepted MCP registration but verification did not match expected ToolNet command."}:{installed:!0,changed:!0,serverName:o,command:t,args:["mcp"]}}import{existsSync as cn,mkdirSync as ln,readFileSync as un,renameSync as dn,rmSync as mn,writeFileSync as pn}from"node:fs";import{dirname as gn}from"node:path";function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function fn(e){return/^[A-Za-z0-9_./:-]+$/u.test(e)?e:`'${e.replace(/'/gu,"'\\''")}'`}function yn(e){if(!cn(e))return{};let t;try{t=JSON.parse(un(e,"utf8"))}catch(n){throw new Error(`Invalid existing Claude settings.json: ${n instanceof Error?n.message:String(n)}`)}if(!x(t))throw new Error("Invalid existing Claude settings.json: root must be a JSON object.");return t}function B(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("Invalid existing Claude settings.json: hook event must be an array.");let t=[];for(let n of e){if(!x(n)){t.push(n);continue}let o=n.hooks;if(!Array.isArray(o)){t.push(n);continue}let s=o.filter(i=>{if(!x(i))return!0;let r=i.command;return!(typeof r=="string"&&r.includes("session:claude-hook"))});s.length!==0&&t.push({...n,hooks:s})}return t}function H(e){return{type:"command",command:e,timeout:10}}function hn(e,t){ln(gn(e),{recursive:!0,mode:448});let n=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{pn(n,JSON.stringify(t,null,2)+`
1068
+ `,{encoding:"utf8",mode:384}),dn(n,e)}finally{mn(n,{force:!0})}}function Le(e={}){let t=e.settingsFile??ie(),n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=yn(t),s=o.hooks;if(s!==void 0&&!x(s))throw new Error("Invalid existing Claude settings.json: hooks must be an object.");let i=x(s)?{...s}:{},r=`${fn(n)} session:claude-hook`,a=B(i.SessionStart);a.push({matcher:"startup|resume|clear|compact",hooks:[H(r)]}),i.SessionStart=a;let l=B(i.PostToolUse);l.push({matcher:"Edit|Write",hooks:[H(r)]}),i.PostToolUse=l;let c=B(i.Stop);c.push({hooks:[H(r)]}),i.Stop=c;let u={...o,hooks:i},p=JSON.stringify(o),d=JSON.stringify(u);return p===d?{settingsFile:t,changed:!1}:(hn(t,u),{settingsFile:t,changed:!0})}import{existsSync as vn,mkdirSync as bn,readFileSync as xn,renameSync as Sn,rmSync as wn,writeFileSync as On}from"node:fs";import{dirname as Cn}from"node:path";function S(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Je(e){if(!vn(e))return{};let t;try{t=JSON.parse(xn(e,"utf8"))}catch(n){throw new Error(`Invalid existing Claude Code config: ${n instanceof Error?n.message:String(n)}`)}if(!S(t))throw new Error("Invalid existing Claude Code config: root must be a JSON object.");return t}function Be(e,t){if(!S(e))return!1;let n=e.args;return e.type==="stdio"&&e.command===t&&Array.isArray(n)&&n.length===1&&n[0]==="mcp"}function Mn(e,t){bn(Cn(e),{recursive:!0});let n=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{On(n,JSON.stringify(t,null,2)+`
1069
+ `,{encoding:"utf8",mode:384}),Sn(n,e)}finally{wn(n,{force:!0})}}function He(e={}){let t=e.stateFile??ae(),n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",s=Je(t),i=s.mcpServers;if(i!==void 0&&!S(i))throw new Error("Invalid existing Claude Code config: mcpServers must be an object.");let r=S(i)?{...i}:{},a=r[o];if(Be(a,n))return{installed:!0,changed:!1,configFile:t,serverName:o,command:[n,"mcp"],repaired:!1};let l=a!==void 0;r[o]={type:"stdio",command:n,args:["mcp"]},Mn(t,{...s,mcpServers:r});let u=Je(t).mcpServers;if(!S(u)||!Be(u[o],n))throw new Error("Claude Code MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:t,serverName:o,command:[n,"mcp"],repaired:l}}function Ye(e={}){let t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=Le({binary:t,settingsFile:e.settingsFile}),o=He({binary:t,stateFile:e.stateFile});return{hooks:n,mcp:o,files:[n.settingsFile,o.configFile]}}function ze(){return ce()}function Y(e={}){let t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=[],o=ze(),s=new Map(o.map(i=>[i.agent,i.detected]));if(!(e.force===!0||s.get("agy")===!0))n.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let r=ve({binary:t});n.push({agent:"agy",detected:!0,installed:!0,targets:r.files})}catch(r){n.push({agent:"agy",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||s.get("opencode")===!0))n.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let r=Oe({binary:t}),a=Ee({binary:t});n.push({agent:"opencode",detected:!0,installed:!0,targets:[r,a.configFile,`mcp:${a.serverName}`]})}catch(r){n.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||s.get("claude")===!0))n.push({agent:"claude",detected:!1,installed:!1,targets:[]});else try{let r=Ye({binary:t});n.push({agent:"claude",detected:!0,installed:!0,targets:[r.hooks.settingsFile,r.mcp.configFile,`mcp:${r.mcp.serverName}`]})}catch(r){n.push({agent:"claude",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||s.get("codex")===!0))n.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let r=Ne({binary:t}),a=Pe({binary:t}),l=$e({binary:t});if(!l.installed)throw new Error(l.error??"Codex MCP registration failed");let c=[r.configFile,a,`mcp:${l.serverName}`];r.preservedPrevious&&c.push(r.previousFile),n.push({agent:"codex",detected:!0,installed:!0,targets:c})}catch(r){n.push({agent:"codex",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}return n}function In(e){console.log(""),console.log("ToolNet Memory Integration Detection"),console.log("===================================="),console.log("");for(let t of e){let n=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":t.agent==="claude"?"Claude Code":"Codex";if(!t.detected){console.log(`\u25CB ${n}: not detected`);continue}console.log(`\u2713 ${n}: detected`);for(let o of t.evidence)console.log(` ${o}`)}console.log("")}function An(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let t of e){let n=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":"Codex";if(!t.detected){console.log(`- ${n}: not detected`);continue}if(t.installed){console.log(`\u2713 ${n}: automatic memory enabled`);continue}console.log(`\u2717 ${n}: integration failed`),t.error&&console.log(` ${t.error}`)}console.log("")}async function En(){let e=process.argv.slice(2),t=e.includes("--all"),n=e.includes("--json");if(e.includes("--detect-only")){let i=ze();if(n){console.log(JSON.stringify(i,null,2));return}In(i);return}let s=Y({force:t});if(n){console.log(JSON.stringify(s,null,2));return}An(s)}var Tn=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Tn&&En().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});function Nn(e=process.cwd()){let t=Rn(e);if(!Ue(t))throw new Error(`Project path does not exist: ${t}`);if(!kn(t).isDirectory())throw new Error(`Project path is not a directory: ${t}`);let n=new O().detect(t),o=jn(n.rootPath,".toolnet","project.json");if(!Ue(o))throw new Error(`ToolNet project initialization failed: ${o} was not created`);return{initialized:!0,project:{id:n.id,name:n.name,remote:n.remote,rootPath:n.rootPath},manifestFile:o}}function _n(e,t){let n=e.indexOf(t);return n>=0?e[n+1]:void 0}async function Pn(){let e=process.argv.slice(2),t=e.includes("--json"),n=!e.includes("--no-integrate"),o=_n(e,"--project"),s=e.find((l,c)=>!l.startsWith("-")&&(c===0||e[c-1]!=="--project")),i=o??s??process.cwd(),r=await N("Initializing ToolNet project",()=>Nn(i),{enabled:!t}),a=[];if(n&&(a=await N("Detecting AI coding agents",()=>Y(),{enabled:!t})),t){console.log(JSON.stringify({...r,integrations:a},null,2));return}if(console.log(""),console.log("ToolNet Memory"),console.log("=============="),console.log(""),console.log("\u2713 Project initialized"),console.log(""),console.log(`Project: ${r.project.name}`),console.log(`ID: ${r.project.id}`),console.log(`Root: ${r.project.rootPath}`),console.log(`Manifest: ${r.manifestFile}`),console.log(""),n){console.log("AI integrations:");let l=a.filter(c=>c.detected&&c.installed);if(!l.length)console.log(" \u25CB No supported coding agent detected");else for(let c of l){let u=c.agent==="agy"?"Agy / Antigravity":c.agent==="opencode"?"OpenCode":"Codex";console.log(` \u2713 ${u}`)}console.log("")}console.log("Next: toolnet-memory doctor"),console.log("")}var Fn=process.argv[1]?.endsWith("/init.js")||process.argv[1]?.endsWith("/init.ts");Fn&&Pn().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});export{Nn as initializeToolNetProject};