toolnet-memory 0.3.3 → 0.3.4

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,269 +1,25 @@
1
- import{existsSync as Ae}from"node:fs";import{homedir as Te}from"node:os";import{join as Me}from"node:path";import{spawnSync as Fe}from"node:child_process";import{homedir as je}from"node:os";import{join as p}from"node:path";function P(e={}){return p(e.home??je(),".gemini")}function k(e={}){return p(P(e),"config")}function v(e={}){return p(k(e),"mcp_config.json")}function C(e={}){return p(k(e),"hooks.json")}function _(e={}){return p(P(e),"antigravity-cli")}function $(e="toolnet-memory",n={}){return p(_(n),"plugins",e)}function J(e={}){return[_(e),k(e)]}import{homedir as Ee}from"node:os";import{join as y}from"node:path";function g(e={}){let n=e.xdgConfigHome??process.env.XDG_CONFIG_HOME?.trim();return n?y(n,"opencode"):y(e.home??Ee(),".config","opencode")}function D(e={}){return y(g(e),"opencode.json")}function H(e={}){return y(g(e),"plugins")}function L(e={}){return y(g(e),"AGENTS.md")}import{homedir as B}from"node:os";import{join as N}from"node:path";function I(e={}){return N(e.home??B(),".claude")}function Y(e={}){return N(I(e),"settings.json")}function U(e={}){return N(e.home??B(),".claude.json")}function Re(e){return Fe("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function S(e){let n=e.commandExists(e.command),t=e.configPaths.filter(s=>Ae(s)),o=t.length>0,i=[];n&&i.push(`command:${e.command}`);for(let s of t)i.push(`config:${s}`);return{agent:e.agent,detected:n||o,commandDetected:n,configDetected:o,evidence:i}}function q(e={}){let n=e.home??Te(),t=e.commandExists??Re,o=e.codexHome??process.env.CODEX_HOME??Me(n,".codex");return[S({agent:"agy",command:"agy",commandExists:t,configPaths:J({home:n})}),S({agent:"opencode",command:"opencode",commandExists:t,configPaths:[g({home:n,xdgConfigHome:e.xdgConfigHome})]}),S({agent:"claude",command:"claude",commandExists:t,configPaths:[I({home:n})]}),S({agent:"codex",command:"codex",commandExists:t,configPaths:[o]})]}import{existsSync as w,mkdirSync as z,readFileSync as Q,renameSync as Ke,writeFileSync as ze}from"node:fs";import{dirname as Qe,join as b}from"node:path";import{existsSync as Pe,mkdirSync as _e,readFileSync as $e,renameSync as Je,rmSync as De,writeFileSync as He}from"node:fs";import{dirname as Le}from"node:path";function Be(e){return`'${e.replace(/'/g,"'\\''")}'`}function G(e={}){let n=e.hooksFile??C();_e(Le(n),{recursive:!0,mode:448});let t={};if(Pe(n)){let r;try{r=JSON.parse($e(n,"utf8"))}catch(c){throw new Error(`Invalid existing Agy hooks.json: ${c instanceof Error?c.message:String(c)}`)}if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("Invalid existing Agy hooks.json: root must be a JSON object.");t=r}let o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`${Be(o)} session:agy-hook`;t["toolnet-memory"]={enabled:!0,PreToolUse:[{matcher:"view_file|list_dir|find_by_name|grep_search|run_command",hooks:[{type:"command",command:`${i} pre-tool`,timeout:5}]}],PreInvocation:[{type:"command",command:`${i} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${i} post`,timeout:15}],Stop:[{type:"command",command:`${i} stop`,timeout:30}]};let s=`${n}.tmp-${process.pid}-${Date.now()}`;try{He(s,JSON.stringify(t,null,2)+`
2
- `,{encoding:"utf8",mode:384}),Je(s,n)}finally{De(s,{force:!0})}return n}import{existsSync as Ye,mkdirSync as Ue,readFileSync as qe,renameSync as Ge,writeFileSync as We}from"node:fs";import{dirname as Xe}from"node:path";function h(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ve(e,n){Ue(Xe(e),{recursive:!0,mode:448});let t=`${e}.tmp-${process.pid}-${Date.now()}`;We(t,`${JSON.stringify(n,null,2)}
3
- `,{encoding:"utf8",mode:384}),Ge(t,e)}function W(e){if(!Ye(e))return{};let n=qe(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing Agy MCP config: ${o instanceof Error?o.message:String(o)}`)}if(!h(t))throw new Error("Invalid existing Agy MCP config: root must be a JSON object.");return t}function X(e,n){return h(e)?e.command===n&&Array.isArray(e.args)&&e.args.length===1&&e.args[0]==="mcp":!1}function V(e={}){let n=e.configFile??v(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=W(n),s=i.mcpServers;if(s!==void 0&&!h(s))throw new Error("Invalid existing Agy MCP config: mcpServers must be an object.");let r=h(s)?{...s}:{},c=r[o];if(X(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:t,args:["mcp"]};r[o]={command:t,args:["mcp"]};let l={...i,mcpServers:r};Ve(n,l);let a=W(n).mcpServers;if(!h(a)||!X(a[o],t))throw new Error("Agy MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:t,args:["mcp"]}}var Ze=`# ToolNet Memory Continuity
1
+ import{existsSync as y}from"node:fs";import{homedir as h}from"node:os";import{join as x}from"node:path";import{spawnSync as ee}from"node:child_process";import{existsSync as R,mkdirSync as $,readFileSync as M,writeFileSync as P}from"node:fs";import{homedir as _}from"node:os";import{dirname as J,join as B}from"node:path";function H(o){return`'${o.replace(/'/g,"'\\''")}'`}function S(o={}){let t=o.hooksFile??B(_(),".gemini","config","hooks.json");$(J(t),{recursive:!0});let e={};if(R(t))try{e=JSON.parse(M(t,"utf8"))}catch(a){throw new Error(`Invalid existing Agy hooks.json: ${a instanceof Error?a.message:String(a)}`)}let r=o.binary??"toolnet-memory",n=`${H(r)} session:agy-hook`;return e["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}]},P(t,JSON.stringify(e,null,2)+`
2
+ `,{encoding:"utf8",mode:384}),t}import{existsSync as D,mkdirSync as b,readFileSync as L,writeFileSync as C}from"node:fs";import{homedir as p}from"node:os";import{join as g}from"node:path";var k="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",f="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function W(){let o=g(p(),".config","opencode","AGENTS.md");b(g(p(),".config","opencode"),{recursive:!0});let t=`${k}
3
+ ## ToolNet Memory \u2014 Project Continuity
4
4
 
5
- ToolNet Memory is the authoritative continuity layer for previous project work.
5
+ When the current workspace belongs to a ToolNet project, identified by a nearest ancestor containing \`.toolnet/project.json\`:
6
6
 
7
- ## Resume / continue behavior
7
+ 1. Before planning, editing, or running implementation commands, read that project root's \`.toolnet/context/startup.md\`.
8
+ 2. Treat its PROJECT RULES, MISSION, CURRENT OBJECTIVE, rationale, current phase/task, Definition of Done, blockers and next actions as project continuity context.
9
+ 3. Do not restart work already marked completed unless repository evidence proves it must be revisited.
10
+ 4. Verify current repository state before continuing old work.
11
+ 5. If \`.toolnet/context/startup.md\` is missing, run \`toolnet-memory context:sync --project <project-root>\`, then read it.
12
+ 6. Never invent missing rationale. If ToolNet says a reason was not explicitly recorded, inspect the implementation or ask rather than guessing.
8
13
 
9
- Whenever the user asks to continue, resume, finish, pick up, return to, or complete previous work:
14
+ This applies automatically to every ToolNet-managed project.
15
+ ${f}`,e=D(o)?L(o,"utf8"):"",r=e.indexOf(k),n=e.indexOf(f);return r>=0&&n>=r?e=e.slice(0,r)+t+e.slice(n+f.length):(e=e.trimEnd(),e&&(e+=`
10
16
 
11
- 1. FIRST call the ToolNet Memory MCP tool \`memory_agent_ask\`.
12
- 2. Use ToolNet's compact continuity result to determine:
13
- - current task
14
- - completed work
15
- - current or last file
16
- - TODOs
17
- - blockers
18
- - next action
19
- 3. Only AFTER continuity is known may you inspect current source or git to verify repository truth.
17
+ `),e+=t),C(o,e.trimEnd()+`
18
+ `,{encoding:"utf8",mode:384}),o}function I(o={}){let t=o.directory??g(p(),".config","opencode","plugins");b(t,{recursive:!0}),W();let e=g(t,"toolnet-memory.js"),r=o.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`// Generated by ToolNet Memory.
19
+ // OpenCode session capture + continuity bootstrap.
20
20
 
21
- ## Forbidden continuity recovery
22
-
23
- Do NOT reconstruct previous work by reading, listing, searching, or shelling into:
24
-
25
- - \`.toolnet/sessions/**\`
26
- - \`state.json\`
27
- - \`events.jsonl\`
28
- - raw transcripts
29
- - \`~/.gemini/antigravity-cli/brain/**\`
30
- - Antigravity \`transcript.jsonl\`
31
- - another coding agent's internal session history
32
-
33
- Do NOT run Bash/cat/tail/grep against those locations to discover previous work.
34
-
35
- Do NOT search the filesystem for the implementation or schema of \`memory_agent_ask\`.
36
- Invoke the MCP tool directly.
37
-
38
- For direct continuity facts, prefer \`mode="local"\`.
39
- Use \`mode="ai"\` only when continuity is ambiguous or requires synthesis.
40
-
41
- Current repository evidence overrides stale memory after ToolNet has restored the working context.
42
-
43
- Do not ask the user to repeat context already available through ToolNet Memory.
44
- `;function Z(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function j(e,n){z(Qe(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ze(t,n,{encoding:"utf8",mode:384}),Ke(t,e)}function K(e,n){w(e)&&Q(e,"utf8")===n||j(e,n)}function ee(e){if(!w(e))return{};let n=Q(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid legacy Antigravity config ${e}: ${o instanceof Error?o.message:String(o)}`)}if(!Z(t))throw new Error(`Invalid legacy Antigravity config ${e}: root must be object`);return t}function et(e,n){if(!w(e))return!1;let t=ee(e);if(!Z(t.mcpServers)||!Object.prototype.hasOwnProperty.call(t.mcpServers,n))return!1;let o={...t.mcpServers};return delete o[n],j(e,`${JSON.stringify({...t,mcpServers:o},null,2)}
45
- `),!0}function tt(e){if(!w(e))return!1;let n=ee(e);if(!Object.prototype.hasOwnProperty.call(n,"toolnet-memory"))return!1;let t={...n};return delete t["toolnet-memory"],j(e,`${JSON.stringify(t,null,2)}
46
- `),!0}function te(e={}){let n=e.pluginName??"toolnet-memory",t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.pluginRoot??$(n),i=b(o,"plugin.json"),s=b(o,"mcp_config.json"),r=b(o,"hooks.json"),c=b(o,"rules","toolnet-memory-continuity.md");z(o,{recursive:!0,mode:448}),K(i,`${JSON.stringify({$schema:"https://antigravity.google/schemas/v1/plugin.json",name:n,description:"Persistent project continuity and memory for Antigravity coding sessions."},null,2)}
47
- `),V({configFile:s,binary:t,serverName:"toolnet-memory"}),G({hooksFile:r,binary:t}),K(c,`${Ze.trim()}
48
- `);let l=e.legacyMcpFile??v(),u=e.legacyHooksFile??C(),a=[];return l!==s&&et(l,"toolnet-memory")&&a.push(l),u!==r&&tt(u)&&a.push(u),{installed:!0,pluginRoot:o,files:[i,s,r,c],migratedLegacy:a}}import{existsSync as ot,mkdirSync as re,readFileSync as rt,writeFileSync as se}from"node:fs";import{join as st}from"node:path";var nt="memory_agent_ask";function ne(){return`
49
- [TOOLNET MEMORY AGENT]
50
-
51
- Tool available:
52
- - ${nt}
53
-
54
- Use it automatically BEFORE guessing when:
55
-
56
- - The user asks to continue, resume, or pick up previous work.
57
- - The user says things like:
58
- - "ti\u1EBFp t\u1EE5c task l\xFAc n\xE3y"
59
- - "l\xE0m ti\u1EBFp ph\u1EA7n \u0111ang d\u1EDF"
60
- - "agent tr\u01B0\u1EDBc \u0111ang l\xE0m g\xEC?"
61
- - "d\u1EEBng \u1EDF \u0111\xE2u?"
62
- - "todo n\xE0o ch\u01B0a xong?"
63
- - "continue the previous task"
64
- - "resume the last session"
65
- - Previous-agent state, unfinished work, blockers,
66
- decisions, touched files, or next actions are unclear.
67
- - Fast startup context is not enough to safely continue.
68
-
69
- Preferred mode:
70
-
71
- - mode="local"
72
- for direct factual questions such as:
73
- current task, last file, blocker, completed TODOs.
74
-
75
- - mode="ai"
76
- for composite or ambiguous continuity questions
77
- that benefit from ToolNet Memory Agent reasoning.
78
-
79
- Do NOT call it automatically when:
80
-
81
- - Normal startup context already gives enough information.
82
- - The question is unrelated to previous project work.
83
- - The answer is obvious from current repository evidence.
84
-
85
- Rules:
86
-
87
- - Never invent previous work.
88
- - Current repository evidence overrides stale memory.
89
- - NEVER reconstruct previous work by reading ToolNet internal session files.
90
- - NEVER read/list/search .toolnet/sessions/**, session state.json,
91
- events.jsonl, or raw transcripts to discover previous-agent state.
92
- - Do not search the filesystem for the implementation/schema of
93
- memory_agent_ask. Invoke the MCP tool directly when deeper
94
- continuity is required.
95
- - Do not dump raw transcripts or full memory.
96
- - After receiving the ToolNet answer, continue the task
97
- instead of asking the user to repeat known context.
98
- - If ToolNet says information is not recorded, say so.
99
- `.trim()}var oe="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",E="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function it(){let e=L();re(g(),{recursive:!0});let n=`${oe}
100
- ## ToolNet Memory
101
-
102
- ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
103
-
104
- Rules:
105
-
106
- 1. Do not create a ToolNet project automatically.
107
- 2. If no valid \`.toolnet/project.json\` exists in the current project or an ancestor, ignore ToolNet Memory.
108
- 3. Normal startup context must stay small and selective.
109
- 4. Do not automatically run deep recovery commands.
110
- 5. Use ToolNet MCP or selective retrieval when older project knowledge is actually needed.
111
- 6. Raw transcripts must not be injected into prompts.
112
- 7. Current repository evidence has priority over stale memory when they conflict.
113
-
114
-
115
- ${ne()}
116
-
117
- ${E}`,t=ot(e)?rt(e,"utf8"):"",o=t.indexOf(oe),i=t.indexOf(E);return o>=0&&i>=o?t=t.slice(0,o)+n+t.slice(i+E.length):(t=t.trimEnd(),t&&(t+=`
118
-
119
- `),t+=n),se(e,t.trimEnd()+`
120
- `,{encoding:"utf8",mode:384}),e}function ie(e={}){let n=e.directory??H();re(n,{recursive:!0}),it();let t=st(n,"toolnet-memory.js"),o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`
121
- // Generated by ToolNet Memory.
122
- // OpenCode integration:
123
- // - project gated
124
- // - selective context injection
125
- // - incremental session checkpoints
126
- // - periodic durability sync
127
- // - best-effort shutdown flush
128
-
129
- import fs from "node:fs"
130
- import path from "node:path"
131
- import os from "node:os"
132
-
133
- const TOOLNET_BINARY =
134
- ${JSON.stringify(o)}
135
-
136
- const CONTEXT_MAX_TOKENS = 700
137
-
138
- const CONTEXT_CACHE_MS = 5000
139
-
140
- const PERIODIC_SYNC_MS = 30000
141
-
142
- const SYNC_TIMEOUT_MS = 15000
143
-
144
- const STATUS_FILE = path.join(
145
- os.homedir(),
146
- ".config",
147
- "toolnet-memory",
148
- "opencode-sync-status.json"
149
- )
150
-
151
- function writeStatus(data) {
152
- try {
153
- fs.mkdirSync(
154
- path.dirname(
155
- STATUS_FILE
156
- ),
157
- {
158
- recursive: true,
159
- }
160
- )
161
-
162
- fs.writeFileSync(
163
- STATUS_FILE,
164
- JSON.stringify(
165
- {
166
- timestamp:
167
- new Date()
168
- .toISOString(),
169
- ...data,
170
- },
171
- null,
172
- 2
173
- ) + "\\n"
174
- )
175
- } catch {
176
- // Status reporting must
177
- // never break OpenCode.
178
- }
179
- }
180
-
181
- function validProjectManifest(
182
- file
183
- ) {
184
- try {
185
- const parsed =
186
- JSON.parse(
187
- fs.readFileSync(
188
- file,
189
- "utf8"
190
- )
191
- )
192
-
193
- return Boolean(
194
- parsed &&
195
- typeof parsed ===
196
- "object" &&
197
- typeof parsed.id ===
198
- "string" &&
199
- parsed.id.length > 0
200
- )
201
- } catch {
202
- return false
203
- }
204
- }
205
-
206
- function findProjectRoot(
207
- inputDirectory
208
- ) {
209
- let current =
210
- path.resolve(
211
- inputDirectory
212
- )
213
-
214
- const filesystemRoot =
215
- path.parse(
216
- current
217
- ).root
218
-
219
- while (true) {
220
- const manifest =
221
- path.join(
222
- current,
223
- ".toolnet",
224
- "project.json"
225
- )
226
-
227
- if (
228
- fs.existsSync(
229
- manifest
230
- ) &&
231
- validProjectManifest(
232
- manifest
233
- )
234
- ) {
235
- return current
236
- }
237
-
238
- if (
239
- current ===
240
- filesystemRoot
241
- ) {
242
- break
243
- }
244
-
245
- const parent =
246
- path.dirname(
247
- current
248
- )
249
-
250
- if (
251
- parent === current
252
- ) {
253
- break
254
- }
255
-
256
- current = parent
257
- }
258
-
259
- return null
260
- }
261
-
262
- function getSessionId(
263
- event
264
- ) {
265
- const p =
266
- event?.properties ?? {}
21
+ function getSessionId(event) {
22
+ const p = event?.properties ?? {}
267
23
 
268
24
  const candidates = [
269
25
  p.sessionID,
@@ -272,487 +28,155 @@ function getSessionId(
272
28
  p.session?.id,
273
29
  ]
274
30
 
275
- return (
276
- candidates.find(
277
- value =>
278
- typeof value ===
279
- "string" &&
280
- value.length > 0
281
- ) ?? null
282
- )
31
+ return candidates.find(
32
+ value => typeof value === "string" && value.length > 0
33
+ ) ?? null
283
34
  }
284
35
 
285
- async function runWithTimeout(
286
- args,
287
- {
288
- stdout = "ignore",
289
- timeout =
290
- SYNC_TIMEOUT_MS,
291
- } = {}
292
- ) {
293
- const child =
294
- Bun.spawn(
295
- args,
296
- {
297
- stdin:
298
- "ignore",
299
- stdout,
300
- stderr:
301
- "ignore",
302
- }
303
- )
304
-
305
- let timer
306
-
307
- const timeoutPromise =
308
- new Promise(
309
- (_, reject) => {
310
- timer =
311
- setTimeout(
312
- () => {
313
- try {
314
- child.kill()
315
- } catch {}
316
-
317
- reject(
318
- new Error(
319
- "ToolNet command timeout"
320
- )
321
- )
322
- },
323
- timeout
324
- )
325
- }
326
- )
327
-
328
- try {
329
- const exitCode =
330
- await Promise.race([
331
- child.exited,
332
- timeoutPromise,
333
- ])
334
-
335
- if (
336
- typeof exitCode ===
337
- "number" &&
338
- exitCode !== 0
339
- ) {
340
- throw new Error(
341
- \`ToolNet exited with code \${exitCode}\`
36
+ export const ToolNetMemoryPlugin = async ({ directory }) => {
37
+ const binary = ${JSON.stringify(r)}
38
+
39
+ async function refreshContext() {
40
+ try {
41
+ const child = Bun.spawn(
42
+ [
43
+ binary,
44
+ "context:sync",
45
+ "--project",
46
+ directory,
47
+ ],
48
+ {
49
+ stdin: "ignore",
50
+ stdout: "ignore",
51
+ stderr: "ignore",
52
+ }
342
53
  )
343
- }
344
54
 
345
- return child
346
- } finally {
347
- if (timer) {
348
- clearTimeout(
349
- timer
350
- )
55
+ await child.exited
56
+ } catch {
57
+ // Context sync must never break OpenCode startup.
351
58
  }
352
59
  }
353
- }
354
-
355
- export const ToolNetMemoryPlugin =
356
- async ({
357
- directory,
358
- }) => {
359
- /*
360
- * CRITICAL:
361
- * Never auto-create a ToolNet
362
- * project just because OpenCode
363
- * happens to start in /root,
364
- * /tmp, $HOME, etc.
365
- */
366
- const projectRoot =
367
- findProjectRoot(
368
- directory
369
- )
370
60
 
371
- if (!projectRoot) {
372
- writeStatus({
373
- active: false,
374
- reason:
375
- "no-toolnet-project",
376
- directory,
377
- })
378
-
379
- return {}
380
- }
381
-
382
- writeStatus({
383
- active: true,
384
- projectRoot,
385
- state:
386
- "plugin-loaded",
387
- })
388
-
389
- let lastSessionId =
390
- null
391
-
392
- let syncChain =
393
- Promise.resolve()
394
-
395
- let contextCache = {
396
- value: "",
397
- expiresAt: 0,
398
- }
399
-
400
- async function readContext() {
401
- const now =
402
- Date.now()
403
-
404
- if (
405
- contextCache.value &&
406
- now <
407
- contextCache.expiresAt
408
- ) {
409
- return (
410
- contextCache.value
411
- )
412
- }
413
-
414
- try {
415
- const child =
416
- Bun.spawn(
417
- [
418
- TOOLNET_BINARY,
419
- "context:print",
420
- "--project",
421
- projectRoot,
422
- "--tokens",
423
- String(
424
- CONTEXT_MAX_TOKENS
425
- ),
426
- ],
427
- {
428
- stdin:
429
- "ignore",
430
- stdout:
431
- "pipe",
432
- stderr:
433
- "ignore",
434
- }
435
- )
436
-
437
- const textPromise =
438
- new Response(
439
- child.stdout
440
- ).text()
441
-
442
- const exitCode =
443
- await child.exited
444
-
445
- if (
446
- exitCode !== 0
447
- ) {
448
- return ""
61
+ async function readContext() {
62
+ try {
63
+ const child = Bun.spawn(
64
+ [
65
+ binary,
66
+ "context:print",
67
+ "--project",
68
+ directory,
69
+ "--tokens",
70
+ "900",
71
+ ],
72
+ {
73
+ stdin: "ignore",
74
+ stdout: "pipe",
75
+ stderr: "ignore",
449
76
  }
77
+ )
450
78
 
451
- const text =
452
- (
453
- await textPromise
454
- ).trim()
79
+ const text =
80
+ await new Response(
81
+ child.stdout
82
+ ).text()
455
83
 
456
- contextCache = {
457
- value: text,
458
- expiresAt:
459
- now +
460
- CONTEXT_CACHE_MS,
461
- }
84
+ await child.exited
462
85
 
463
- return text
464
- } catch {
465
- return ""
466
- }
86
+ return text.trim()
87
+ } catch {
88
+ return ""
467
89
  }
90
+ }
468
91
 
469
- async function syncNow(
470
- sessionId,
471
- flag,
472
- reason
473
- ) {
474
- if (!sessionId) {
475
- return
476
- }
92
+ function sync(sessionId, flag) {
93
+ if (!sessionId) return
477
94
 
95
+ try {
478
96
  const args = [
479
- TOOLNET_BINARY,
97
+ binary,
480
98
  "session:opencode-sync",
481
99
  sessionId,
482
100
  "--project",
483
- projectRoot,
101
+ directory,
484
102
  ]
485
103
 
486
104
  if (flag) {
487
- args.push(
488
- flag
489
- )
105
+ args.push(flag)
490
106
  }
491
107
 
492
- try {
493
- await runWithTimeout(
494
- args
495
- )
496
-
497
- writeStatus({
498
- active: true,
499
- projectRoot,
500
- sessionId,
501
- reason,
502
- state:
503
- "sync-success",
504
- })
505
- } catch (error) {
506
- writeStatus({
507
- active: true,
508
- projectRoot,
509
- sessionId,
510
- reason,
511
- state:
512
- "sync-failed",
513
- error:
514
- error instanceof
515
- Error
516
- ? error.message
517
- : String(
518
- error
519
- ),
520
- })
108
+ const child = Bun.spawn(args, {
109
+ stdin: "ignore",
110
+ stdout: "ignore",
111
+ stderr: "ignore",
112
+ })
521
113
 
522
- throw error
114
+ if (
115
+ typeof child.unref === "function"
116
+ ) {
117
+ child.unref()
523
118
  }
119
+ } catch {
120
+ // Memory capture must never break OpenCode.
524
121
  }
122
+ }
123
+
124
+ /*
125
+ * Pull the latest project brief from ToolNet remote storage
126
+ * before the new OpenCode session starts doing work.
127
+ */
128
+ await refreshContext()
129
+
130
+ return {
131
+ event: async ({ event }) => {
132
+ const sessionId =
133
+ getSessionId(event)
525
134
 
526
- function queueSync(
527
- sessionId,
528
- flag,
529
- reason
530
- ) {
531
135
  if (!sessionId) {
532
- return syncChain
136
+ return
533
137
  }
534
138
 
535
- lastSessionId =
536
- sessionId
139
+ if (event.type === "session.idle") {
140
+ sync(sessionId, "--idle")
141
+ return
142
+ }
537
143
 
538
- syncChain =
539
- syncChain
540
- .catch(
541
- () =>
542
- undefined
543
- )
544
- .then(
545
- () =>
546
- syncNow(
547
- sessionId,
548
- flag,
549
- reason
550
- )
551
- )
144
+ if (event.type === "session.compacted") {
145
+ sync(sessionId, "--compacted")
146
+ return
147
+ }
552
148
 
553
- return syncChain
554
- }
149
+ if (event.type === "session.error") {
150
+ sync(sessionId, "--error")
151
+ }
152
+ },
555
153
 
556
154
  /*
557
- * Periodic durability checkpoint.
558
- *
559
- * This is intentionally independent
560
- * from session.idle/dispose because
561
- * abrupt SSH/process termination may
562
- * skip shutdown hooks.
563
- *
564
- * unref() prevents the timer itself
565
- * from keeping OpenCode alive.
155
+ * Official OpenCode compaction hook.
156
+ * Preserve ToolNet's semantic state after context compaction.
566
157
  */
567
- const periodic =
568
- setInterval(
569
- () => {
570
- if (
571
- lastSessionId
572
- ) {
573
- void queueSync(
574
- lastSessionId,
575
- null,
576
- "periodic"
577
- )
578
- }
579
- },
580
- PERIODIC_SYNC_MS
581
- )
582
-
583
- if (
584
- typeof periodic.unref ===
585
- "function"
586
- ) {
587
- periodic.unref()
588
- }
589
-
590
- return {
591
- event: async ({
592
- event,
593
- }) => {
594
- const sessionId =
595
- getSessionId(
596
- event
597
- )
598
-
599
- if (sessionId) {
600
- lastSessionId =
601
- sessionId
602
- }
603
-
604
- if (
605
- event.type ===
606
- "session.idle"
607
- ) {
608
- await queueSync(
609
- sessionId,
610
- "--idle",
611
- "session.idle"
612
- )
613
-
614
- return
615
- }
616
-
617
- if (
618
- event.type ===
619
- "session.compacted"
620
- ) {
621
- await queueSync(
622
- sessionId,
623
- "--compacted",
624
- "session.compacted"
625
- )
626
-
627
- contextCache = {
628
- value: "",
629
- expiresAt: 0,
630
- }
631
-
632
- return
633
- }
634
-
635
- if (
636
- event.type ===
637
- "session.error"
638
- ) {
639
- await queueSync(
640
- sessionId,
641
- "--error",
642
- "session.error"
643
- )
158
+ "experimental.session.compacting": async (_input, output) => {
159
+ const context =
160
+ await readContext()
644
161
 
645
- return
646
- }
647
- },
648
-
649
- /*
650
- * VERIFIED on OpenCode 1.18.14:
651
- * this hook fires on real model turns.
652
- *
653
- * It remains experimental, therefore
654
- * AGENTS.md + MCP stay as fallbacks.
655
- */
656
- "experimental.chat.system.transform":
657
- async (
658
- _input,
659
- output
660
- ) => {
661
- const context =
662
- await readContext()
663
-
664
- if (!context) {
665
- return
666
- }
667
-
668
- if (
669
- Array.isArray(
670
- output?.system
671
- )
672
- ) {
673
- output.system.push(
674
- context
675
- )
676
-
677
- return
678
- }
679
-
680
- if (
681
- typeof output?.system ===
682
- "string"
683
- ) {
684
- output.system =
685
- output.system
686
- ? \`\${output.system}\\n\\n\${context}\`
687
- : context
688
- }
689
- },
690
-
691
- /*
692
- * Optional compaction survival hook.
693
- * We keep it as a supplementary path,
694
- * never as the only continuity path.
695
- */
696
- "experimental.session.compacting":
697
- async (
698
- _input,
699
- output
700
- ) => {
701
- const context =
702
- await readContext()
703
-
704
- if (
705
- context &&
706
- Array.isArray(
707
- output?.context
708
- )
709
- ) {
710
- output.context.push(
711
- context
712
- )
713
- }
714
- },
715
-
716
- /*
717
- * VERIFIED for clean OpenCode exits.
718
- * Best effort only: kill -9 / crash /
719
- * machine loss cannot guarantee this.
720
- */
721
- dispose: async () => {
722
- clearInterval(
723
- periodic
162
+ if (
163
+ context &&
164
+ Array.isArray(
165
+ output.context
724
166
  )
725
-
726
- if (
727
- lastSessionId
728
- ) {
729
- try {
730
- await queueSync(
731
- lastSessionId,
732
- "--idle",
733
- "dispose"
734
- )
735
- } catch {
736
- // Status already recorded.
737
- }
738
- }
739
-
740
- try {
741
- await syncChain
742
- } catch {
743
- // Do not block OpenCode shutdown.
744
- }
745
- },
746
- }
167
+ ) {
168
+ output.context.push(
169
+ context
170
+ )
171
+ }
172
+ },
747
173
  }
748
- `;return se(t,i.trimStart(),{encoding:"utf8",mode:384}),t}import{existsSync as le,mkdirSync as ct,readFileSync as at,renameSync as lt,writeFileSync as ut}from"node:fs";import{dirname as ue,join as dt}from"node:path";function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function gt(e,n){ct(ue(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ut(t,`${JSON.stringify(n,null,2)}
749
- `,{encoding:"utf8",mode:384}),lt(t,e)}function ce(e){if(!le(e))return{};let n=at(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing OpenCode opencode.json: ${o instanceof Error?o.message:String(o)}`)}if(!f(t))throw new Error("Invalid existing OpenCode opencode.json: root must be a JSON object.");return t}function ae(e,n){if(!f(e))return!1;let t=e.command;return e.type==="local"&&e.enabled!==!1&&Array.isArray(t)&&t.length===2&&t[0]===n&&t[1]==="mcp"}function mt(e,n){let t=e.mcpServers;if(!f(t)||!Object.prototype.hasOwnProperty.call(t,n))return{root:e,changed:!1};let o={...t};return delete o[n],{root:{...e,mcpServers:o},changed:!0}}function de(e={}){let n=e.configFile??D(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=dt(ue(n),"opencode.jsonc"),s=le(i)?i:void 0,r=ce(n),c=mt(r,o),l=c.root,u=l.mcp;if(u!==void 0&&!f(u))throw new Error("Invalid existing OpenCode config: mcp must be an object.");let a=f(u)?{...u}:{},m=a[o];if(ae(m,t)&&!c.changed)return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s};a[o]={type:"local",command:[t,"mcp"],enabled:!0};let d={...l,mcp:a};gt(n,d);let R=ce(n);if(!f(R.mcp)||!ae(R.mcp[o],t))throw new Error("OpenCode MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s}}import{existsSync as pt,mkdirSync as ge,readFileSync as ft,writeFileSync as me}from"node:fs";import{homedir as pe}from"node:os";import{dirname as fe,join as A}from"node:path";function yt(e){let n=[],t=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,o;for(;o=t.exec(e);){let i=o[1]??o[2]??"";try{n.push(o[1]!==void 0?JSON.parse(`"${i}"`):i)}catch{n.push(i)}}return n}function ye(e={}){let n=e.configFile??A(process.env.CODEX_HOME??A(pe(),".codex"),"config.toml"),t=e.previousFile??A(pe(),".config","toolnet-memory","codex-notify-previous.json");ge(fe(n),{recursive:!0}),ge(fe(t),{recursive:!0});let o=pt(n)?ft(n,"utf8"):"",i=e.binary??"toolnet-memory",s=`notify = [${JSON.stringify(i)}, "session:codex-notify"]`,r=o.split(`
750
- `),c=r.findIndex(d=>/^\s*\[/.test(d));c<0&&(c=r.length);let l=-1,u=-1;for(let d=0;d<c;d+=1)if(/^\s*notify\s*=/.test(r[d])){if(l=d,u=d,r[d].includes("[")&&!r[d].includes("]"))for(;u+1<c&&(u+=1,!r[u].includes("]")););break}let a=[];if(l>=0){let d=r.slice(l,u+1).join(`
751
- `);a=yt(d),r.splice(l,u-l+1,s)}else c=r.findIndex(d=>/^\s*\[/.test(d)),c<0&&(c=r.length),r.splice(c,0,s);let m=a.length>=2&&a[a.length-1]==="session:codex-notify";return a.length>0&&!m&&me(t,JSON.stringify(a,null,2)+`
752
- `,{encoding:"utf8",mode:384}),o=r.join(`
753
- `),o.endsWith(`
754
- `)||(o+=`
755
- `),me(n,o,{encoding:"utf8",mode:384}),{configFile:n,previousFile:t,preservedPrevious:a.length>0&&!m}}import{existsSync as ht,mkdirSync as Ot,readFileSync as xt,writeFileSync as vt}from"node:fs";import{homedir as Ct}from"node:os";import{dirname as St,join as he}from"node:path";function bt(e){return`'${e.replace(/'/g,"'\\''")}'`}function Oe(e={}){let n=e.hooksFile??he(process.env.CODEX_HOME??he(Ct(),".codex"),"hooks.json");Ot(St(n),{recursive:!0});let t={};if(ht(n))try{t=JSON.parse(xt(n,"utf8"))}catch(c){throw new Error(`Invalid existing Codex hooks.json: ${c instanceof Error?c.message:String(c)}`)}let o=t.hooks&&typeof t.hooks=="object"&&!Array.isArray(t.hooks)?t.hooks:{};t.hooks=o;let s=(Array.isArray(o.SessionStart)?o.SessionStart:[]).filter(c=>{try{return!JSON.stringify(c).includes("session:codex-context")}catch{return!0}}),r=e.binary??"toolnet-memory";return s.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${bt(r)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),o.SessionStart=s,vt(n,JSON.stringify(t,null,2)+`
756
- `,{encoding:"utf8",mode:384}),n}import{spawnSync as wt}from"node:child_process";function T(e,n){return wt(e,n,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function xe(e,n){let t=T(e,["mcp","get",n,"--json"]);if(t.status!==0||!t.stdout)return null;try{return JSON.parse(t.stdout)}catch{return null}}function ve(e,n){return e.enabled!==!1&&e.transport?.type==="stdio"&&e.transport?.command===n&&Array.isArray(e.transport?.args)&&e.transport?.args.length===1&&e.transport.args[0]==="mcp"}function Ce(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=e.codexBinary??"codex",o=e.serverName??"toolnet-memory",i=xe(t,o);if(i&&ve(i,n))return{installed:!0,changed:!1,serverName:o,command:n,args:["mcp"]};if(i){let c=T(t,["mcp","remove",o]);if(c.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(c.stderr||c.stdout||"Unable to remove old ToolNet MCP configuration.").trim()}}let s=T(t,["mcp","add",o,"--",n,"mcp"]);if(s.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(s.stderr||s.stdout||"Unable to register ToolNet MCP.").trim()};let r=xe(t,o);return!r||!ve(r,n)?{installed:!1,changed:!0,serverName:o,command:n,args:["mcp"],error:"Codex accepted MCP registration but verification did not match expected ToolNet command."}:{installed:!0,changed:!0,serverName:o,command:n,args:["mcp"]}}import{existsSync as kt,mkdirSync as Nt,readFileSync as It,renameSync as jt,rmSync as Et,writeFileSync as At}from"node:fs";import{dirname as Tt}from"node:path";function O(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Mt(e){return/^[A-Za-z0-9_./:-]+$/u.test(e)?e:`'${e.replace(/'/gu,"'\\''")}'`}function Ft(e){if(!kt(e))return{};let n;try{n=JSON.parse(It(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude settings.json: ${t instanceof Error?t.message:String(t)}`)}if(!O(n))throw new Error("Invalid existing Claude settings.json: root must be a JSON object.");return n}function M(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 n=[];for(let t of e){if(!O(t)){n.push(t);continue}let o=t.hooks;if(!Array.isArray(o)){n.push(t);continue}let i=o.filter(s=>{if(!O(s))return!0;let r=s.command;return!(typeof r=="string"&&r.includes("session:claude-hook"))});i.length!==0&&n.push({...t,hooks:i})}return n}function F(e){return{type:"command",command:e,timeout:10}}function Rt(e,n){Nt(Tt(e),{recursive:!0,mode:448});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{At(t,JSON.stringify(n,null,2)+`
757
- `,{encoding:"utf8",mode:384}),jt(t,e)}finally{Et(t,{force:!0})}}function Se(e={}){let n=e.settingsFile??Y(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=Ft(n),i=o.hooks;if(i!==void 0&&!O(i))throw new Error("Invalid existing Claude settings.json: hooks must be an object.");let s=O(i)?{...i}:{},r=`${Mt(t)} session:claude-hook`,c=M(s.SessionStart);c.push({matcher:"startup|resume|clear|compact",hooks:[F(r)]}),s.SessionStart=c;let l=M(s.PostToolUse);l.push({matcher:"Edit|Write",hooks:[F(r)]}),s.PostToolUse=l;let u=M(s.Stop);u.push({hooks:[F(r)]}),s.Stop=u;let a={...o,hooks:s},m=JSON.stringify(o),d=JSON.stringify(a);return m===d?{settingsFile:n,changed:!1}:(Rt(n,a),{settingsFile:n,changed:!0})}import{existsSync as Pt,mkdirSync as _t,readFileSync as $t,renameSync as Jt,rmSync as Dt,writeFileSync as Ht}from"node:fs";import{dirname as Lt}from"node:path";function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function be(e){if(!Pt(e))return{};let n;try{n=JSON.parse($t(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude Code config: ${t instanceof Error?t.message:String(t)}`)}if(!x(n))throw new Error("Invalid existing Claude Code config: root must be a JSON object.");return n}function we(e,n){if(!x(e))return!1;let t=e.args;return e.type==="stdio"&&e.command===n&&Array.isArray(t)&&t.length===1&&t[0]==="mcp"}function Bt(e,n){_t(Lt(e),{recursive:!0});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{Ht(t,JSON.stringify(n,null,2)+`
758
- `,{encoding:"utf8",mode:384}),Jt(t,e)}finally{Dt(t,{force:!0})}}function ke(e={}){let n=e.stateFile??U(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=be(n),s=i.mcpServers;if(s!==void 0&&!x(s))throw new Error("Invalid existing Claude Code config: mcpServers must be an object.");let r=x(s)?{...s}:{},c=r[o];if(we(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],repaired:!1};let l=c!==void 0;r[o]={type:"stdio",command:t,args:["mcp"]},Bt(n,{...i,mcpServers:r});let a=be(n).mcpServers;if(!x(a)||!we(a[o],t))throw new Error("Claude Code MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],repaired:l}}function Ne(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=Se({binary:n,settingsFile:e.settingsFile}),o=ke({binary:n,stateFile:e.stateFile});return{hooks:t,mcp:o,files:[t.settingsFile,o.configFile]}}function Ie(){return q()}function Yt(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=[],o=Ie(),i=new Map(o.map(s=>[s.agent,s.detected]));if(!(e.force===!0||i.get("agy")===!0))t.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let r=te({binary:n});t.push({agent:"agy",detected:!0,installed:!0,targets:r.files})}catch(r){t.push({agent:"agy",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("opencode")===!0))t.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let r=ie({binary:n}),c=de({binary:n});t.push({agent:"opencode",detected:!0,installed:!0,targets:[r,c.configFile,`mcp:${c.serverName}`]})}catch(r){t.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("claude")===!0))t.push({agent:"claude",detected:!1,installed:!1,targets:[]});else try{let r=Ne({binary:n});t.push({agent:"claude",detected:!0,installed:!0,targets:[r.hooks.settingsFile,r.mcp.configFile,`mcp:${r.mcp.serverName}`]})}catch(r){t.push({agent:"claude",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("codex")===!0))t.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let r=ye({binary:n}),c=Oe({binary:n}),l=Ce({binary:n});if(!l.installed)throw new Error(l.error??"Codex MCP registration failed");let u=[r.configFile,c,`mcp:${l.serverName}`];r.preservedPrevious&&u.push(r.previousFile),t.push({agent:"codex",detected:!0,installed:!0,targets:u})}catch(r){t.push({agent:"codex",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}return t}function Ut(e){console.log(""),console.log("ToolNet Memory Integration Detection"),console.log("===================================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":n.agent==="claude"?"Claude Code":"Codex";if(!n.detected){console.log(`\u25CB ${t}: not detected`);continue}console.log(`\u2713 ${t}: detected`);for(let o of n.evidence)console.log(` ${o}`)}console.log("")}function qt(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":"Codex";if(!n.detected){console.log(`- ${t}: not detected`);continue}if(n.installed){console.log(`\u2713 ${t}: automatic memory enabled`);continue}console.log(`\u2717 ${t}: integration failed`),n.error&&console.log(` ${n.error}`)}console.log("")}async function Gt(){let e=process.argv.slice(2),n=e.includes("--all"),t=e.includes("--json");if(e.includes("--detect-only")){let s=Ie();if(t){console.log(JSON.stringify(s,null,2));return}Ut(s);return}let i=Yt({force:n});if(t){console.log(JSON.stringify(i,null,2));return}qt(i)}var Wt=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Wt&&Gt().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});export{Ie as detectAutoIntegrations,Yt as installAutoIntegrations};
174
+ }
175
+ `;return C(e,n,{encoding:"utf8",mode:384}),e}import{existsSync as Y,mkdirSync as N,readFileSync as X,writeFileSync as T}from"node:fs";import{homedir as E}from"node:os";import{dirname as w,join as m}from"node:path";function q(o){let t=[],e=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=e.exec(o);){let n=r[1]??r[2]??"";try{t.push(r[1]!==void 0?JSON.parse(`"${n}"`):n)}catch{t.push(n)}}return t}function j(o={}){let t=o.configFile??m(process.env.CODEX_HOME??m(E(),".codex"),"config.toml"),e=o.previousFile??m(E(),".config","toolnet-memory","codex-notify-previous.json");N(w(t),{recursive:!0}),N(w(e),{recursive:!0});let r=Y(t)?X(t,"utf8"):"",n=o.binary??"toolnet-memory",a=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
176
+ `),i=s.findIndex(c=>/^\s*\[/.test(c));i<0&&(i=s.length);let u=-1,d=-1;for(let c=0;c<i;c+=1)if(/^\s*notify\s*=/.test(s[c])){if(u=c,d=c,s[c].includes("[")&&!s[c].includes("]"))for(;d+1<i&&(d+=1,!s[d].includes("]")););break}let l=[];if(u>=0){let c=s.slice(u,d+1).join(`
177
+ `);l=q(c),s.splice(u,d-u+1,a)}else i=s.findIndex(c=>/^\s*\[/.test(c)),i<0&&(i=s.length),s.splice(i,0,a);let O=l.length>=2&&l[l.length-1]==="session:codex-notify";return l.length>0&&!O&&T(e,JSON.stringify(l,null,2)+`
178
+ `,{encoding:"utf8",mode:384}),r=s.join(`
179
+ `),r.endsWith(`
180
+ `)||(r+=`
181
+ `),T(t,r,{encoding:"utf8",mode:384}),{configFile:t,previousFile:e,preservedPrevious:l.length>0&&!O}}import{existsSync as G,mkdirSync as U,readFileSync as V,writeFileSync as z}from"node:fs";import{homedir as K}from"node:os";import{dirname as Q,join as A}from"node:path";function Z(o){return`'${o.replace(/'/g,"'\\''")}'`}function F(o={}){let t=o.hooksFile??A(process.env.CODEX_HOME??A(K(),".codex"),"hooks.json");U(Q(t),{recursive:!0});let e={};if(G(t))try{e=JSON.parse(V(t,"utf8"))}catch(i){throw new Error(`Invalid existing Codex hooks.json: ${i instanceof Error?i.message:String(i)}`)}let r=e.hooks&&typeof e.hooks=="object"&&!Array.isArray(e.hooks)?e.hooks:{};e.hooks=r;let a=(Array.isArray(r.SessionStart)?r.SessionStart:[]).filter(i=>{try{return!JSON.stringify(i).includes("session:codex-context")}catch{return!0}}),s=o.binary??"toolnet-memory";return a.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${Z(s)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),r.SessionStart=a,z(t,JSON.stringify(e,null,2)+`
182
+ `,{encoding:"utf8",mode:384}),t}function v(o){return ee("sh",["-lc",`command -v ${JSON.stringify(o)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function te(){return v("agy")||y(x(h(),".gemini"))}function oe(){return v("opencode")||y(x(h(),".config","opencode"))}function ne(){return v("codex")||y(process.env.CODEX_HOME??x(h(),".codex"))}function re(o={}){let t=o.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",e=[];if(!(o.force===!0||te()))e.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let n=S({binary:t});e.push({agent:"agy",detected:!0,installed:!0,targets:[n]})}catch(n){e.push({agent:"agy",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(o.force===!0||oe()))e.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let n=I({binary:t});e.push({agent:"opencode",detected:!0,installed:!0,targets:[n]})}catch(n){e.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(o.force===!0||ne()))e.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let n=j({binary:t}),a=F({binary:t}),s=[n.configFile,a];n.preservedPrevious&&s.push(n.previousFile),e.push({agent:"codex",detected:!0,installed:!0,targets:s})}catch(n){e.push({agent:"codex",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}return e}function se(o){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let t of o){let e=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":"Codex";if(!t.detected){console.log(`- ${e}: not detected`);continue}if(t.installed){console.log(`\u2713 ${e}: automatic memory enabled`);continue}console.log(`\u2717 ${e}: integration failed`),t.error&&console.log(` ${t.error}`)}console.log("")}async function ie(){let o=process.argv.slice(2),t=o.includes("--all"),e=o.includes("--json"),r=re({force:t});if(e){console.log(JSON.stringify(r,null,2));return}se(r)}var ce=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));ce&&ie().catch(o=>{console.error(o instanceof Error?o.message:String(o)),process.exitCode=1});export{re as installAutoIntegrations};