toolnet-memory 0.2.13 → 0.2.15

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,25 +1,169 @@
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
1
+ import{existsSync as g}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 _,mkdirSync as M,readFileSync as F,writeFileSync as P}from"node:fs";import{homedir as $}from"node:os";import{dirname as D,join as H}from"node:path";function L(o){return`'${o.replace(/'/g,"'\\''")}'`}function O(o={}){let t=o.hooksFile??H($(),".gemini","config","hooks.json");M(D(t),{recursive:!0});let e={};if(_(t))try{e=JSON.parse(F(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=`${L(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 B,mkdirSync as I,readFileSync as J,writeFileSync as T}from"node:fs";import{homedir as m}from"node:os";import{join as p}from"node:path";var C="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",f="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function Y(){let o=p(m(),".config","opencode","AGENTS.md");I(p(m(),".config","opencode"),{recursive:!0});let t=`${C}
3
+ ## ToolNet Memory
4
4
 
5
- When the current workspace belongs to a ToolNet project, identified by a nearest ancestor containing \`.toolnet/project.json\`:
5
+ ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
6
6
 
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.
7
+ Rules:
13
8
 
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+=`
9
+ 1. Do not create a ToolNet project automatically.
10
+ 2. If no valid \`.toolnet/project.json\` exists in the current project or an ancestor, ignore ToolNet Memory.
11
+ 3. Normal startup context must stay small and selective.
12
+ 4. Do not automatically run deep recovery commands.
13
+ 5. Use ToolNet MCP or selective retrieval when older project knowledge is actually needed.
14
+ 6. Raw transcripts must not be injected into prompts.
15
+ 7. Current repository evidence has priority over stale memory when they conflict.
16
16
 
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.
17
+ ${f}`,e=B(o)?J(o,"utf8"):"",r=e.indexOf(C),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+=`
20
18
 
21
- function getSessionId(event) {
22
- const p = event?.properties ?? {}
19
+ `),e+=t),T(o,e.trimEnd()+`
20
+ `,{encoding:"utf8",mode:384}),o}function N(o={}){let t=o.directory??p(m(),".config","opencode","plugins");I(t,{recursive:!0}),Y();let e=p(t,"toolnet-memory.js"),r=o.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`
21
+ // Generated by ToolNet Memory.
22
+ // OpenCode integration:
23
+ // - project gated
24
+ // - selective context injection
25
+ // - incremental session checkpoints
26
+ // - periodic durability sync
27
+ // - best-effort shutdown flush
28
+
29
+ import fs from "node:fs"
30
+ import path from "node:path"
31
+ import os from "node:os"
32
+
33
+ const TOOLNET_BINARY =
34
+ ${JSON.stringify(r)}
35
+
36
+ const CONTEXT_MAX_TOKENS = 700
37
+
38
+ const CONTEXT_CACHE_MS = 5000
39
+
40
+ const PERIODIC_SYNC_MS = 30000
41
+
42
+ const SYNC_TIMEOUT_MS = 15000
43
+
44
+ const STATUS_FILE = path.join(
45
+ os.homedir(),
46
+ ".config",
47
+ "toolnet-memory",
48
+ "opencode-sync-status.json"
49
+ )
50
+
51
+ function writeStatus(data) {
52
+ try {
53
+ fs.mkdirSync(
54
+ path.dirname(
55
+ STATUS_FILE
56
+ ),
57
+ {
58
+ recursive: true,
59
+ }
60
+ )
61
+
62
+ fs.writeFileSync(
63
+ STATUS_FILE,
64
+ JSON.stringify(
65
+ {
66
+ timestamp:
67
+ new Date()
68
+ .toISOString(),
69
+ ...data,
70
+ },
71
+ null,
72
+ 2
73
+ ) + "\\n"
74
+ )
75
+ } catch {
76
+ // Status reporting must
77
+ // never break OpenCode.
78
+ }
79
+ }
80
+
81
+ function validProjectManifest(
82
+ file
83
+ ) {
84
+ try {
85
+ const parsed =
86
+ JSON.parse(
87
+ fs.readFileSync(
88
+ file,
89
+ "utf8"
90
+ )
91
+ )
92
+
93
+ return Boolean(
94
+ parsed &&
95
+ typeof parsed ===
96
+ "object" &&
97
+ typeof parsed.id ===
98
+ "string" &&
99
+ parsed.id.length > 0
100
+ )
101
+ } catch {
102
+ return false
103
+ }
104
+ }
105
+
106
+ function findProjectRoot(
107
+ inputDirectory
108
+ ) {
109
+ let current =
110
+ path.resolve(
111
+ inputDirectory
112
+ )
113
+
114
+ const filesystemRoot =
115
+ path.parse(
116
+ current
117
+ ).root
118
+
119
+ while (true) {
120
+ const manifest =
121
+ path.join(
122
+ current,
123
+ ".toolnet",
124
+ "project.json"
125
+ )
126
+
127
+ if (
128
+ fs.existsSync(
129
+ manifest
130
+ ) &&
131
+ validProjectManifest(
132
+ manifest
133
+ )
134
+ ) {
135
+ return current
136
+ }
137
+
138
+ if (
139
+ current ===
140
+ filesystemRoot
141
+ ) {
142
+ break
143
+ }
144
+
145
+ const parent =
146
+ path.dirname(
147
+ current
148
+ )
149
+
150
+ if (
151
+ parent === current
152
+ ) {
153
+ break
154
+ }
155
+
156
+ current = parent
157
+ }
158
+
159
+ return null
160
+ }
161
+
162
+ function getSessionId(
163
+ event
164
+ ) {
165
+ const p =
166
+ event?.properties ?? {}
23
167
 
24
168
  const candidates = [
25
169
  p.sessionID,
@@ -28,155 +172,484 @@ function getSessionId(event) {
28
172
  p.session?.id,
29
173
  ]
30
174
 
31
- return candidates.find(
32
- value => typeof value === "string" && value.length > 0
33
- ) ?? null
175
+ return (
176
+ candidates.find(
177
+ value =>
178
+ typeof value ===
179
+ "string" &&
180
+ value.length > 0
181
+ ) ?? null
182
+ )
34
183
  }
35
184
 
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
- }
185
+ async function runWithTimeout(
186
+ args,
187
+ {
188
+ stdout = "ignore",
189
+ timeout =
190
+ SYNC_TIMEOUT_MS,
191
+ } = {}
192
+ ) {
193
+ const child =
194
+ Bun.spawn(
195
+ args,
196
+ {
197
+ stdin:
198
+ "ignore",
199
+ stdout,
200
+ stderr:
201
+ "ignore",
202
+ }
203
+ )
204
+
205
+ let timer
206
+
207
+ const timeoutPromise =
208
+ new Promise(
209
+ (_, reject) => {
210
+ timer =
211
+ setTimeout(
212
+ () => {
213
+ try {
214
+ child.kill()
215
+ } catch {}
216
+
217
+ reject(
218
+ new Error(
219
+ "ToolNet command timeout"
220
+ )
221
+ )
222
+ },
223
+ timeout
224
+ )
225
+ }
226
+ )
227
+
228
+ try {
229
+ const exitCode =
230
+ await Promise.race([
231
+ child.exited,
232
+ timeoutPromise,
233
+ ])
234
+
235
+ if (
236
+ typeof exitCode ===
237
+ "number" &&
238
+ exitCode !== 0
239
+ ) {
240
+ throw new Error(
241
+ \`ToolNet exited with code \${exitCode}\`
53
242
  )
243
+ }
54
244
 
55
- await child.exited
56
- } catch {
57
- // Context sync must never break OpenCode startup.
245
+ return child
246
+ } finally {
247
+ if (timer) {
248
+ clearTimeout(
249
+ timer
250
+ )
58
251
  }
59
252
  }
253
+ }
60
254
 
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",
76
- }
255
+ export const ToolNetMemoryPlugin =
256
+ async ({
257
+ directory,
258
+ }) => {
259
+ /*
260
+ * CRITICAL:
261
+ * Never auto-create a ToolNet
262
+ * project just because OpenCode
263
+ * happens to start in /root,
264
+ * /tmp, $HOME, etc.
265
+ */
266
+ const projectRoot =
267
+ findProjectRoot(
268
+ directory
77
269
  )
78
270
 
79
- const text =
80
- await new Response(
81
- child.stdout
82
- ).text()
271
+ if (!projectRoot) {
272
+ writeStatus({
273
+ active: false,
274
+ reason:
275
+ "no-toolnet-project",
276
+ directory,
277
+ })
278
+
279
+ return {}
280
+ }
281
+
282
+ writeStatus({
283
+ active: true,
284
+ projectRoot,
285
+ state:
286
+ "plugin-loaded",
287
+ })
288
+
289
+ let lastSessionId =
290
+ null
83
291
 
84
- await child.exited
292
+ let syncChain =
293
+ Promise.resolve()
85
294
 
86
- return text.trim()
87
- } catch {
88
- return ""
295
+ let contextCache = {
296
+ value: "",
297
+ expiresAt: 0,
89
298
  }
90
- }
91
299
 
92
- function sync(sessionId, flag) {
93
- if (!sessionId) return
300
+ async function readContext() {
301
+ const now =
302
+ Date.now()
303
+
304
+ if (
305
+ contextCache.value &&
306
+ now <
307
+ contextCache.expiresAt
308
+ ) {
309
+ return (
310
+ contextCache.value
311
+ )
312
+ }
313
+
314
+ try {
315
+ const child =
316
+ Bun.spawn(
317
+ [
318
+ TOOLNET_BINARY,
319
+ "context:print",
320
+ "--project",
321
+ projectRoot,
322
+ "--tokens",
323
+ String(
324
+ CONTEXT_MAX_TOKENS
325
+ ),
326
+ ],
327
+ {
328
+ stdin:
329
+ "ignore",
330
+ stdout:
331
+ "pipe",
332
+ stderr:
333
+ "ignore",
334
+ }
335
+ )
336
+
337
+ const textPromise =
338
+ new Response(
339
+ child.stdout
340
+ ).text()
341
+
342
+ const exitCode =
343
+ await child.exited
344
+
345
+ if (
346
+ exitCode !== 0
347
+ ) {
348
+ return ""
349
+ }
350
+
351
+ const text =
352
+ (
353
+ await textPromise
354
+ ).trim()
355
+
356
+ contextCache = {
357
+ value: text,
358
+ expiresAt:
359
+ now +
360
+ CONTEXT_CACHE_MS,
361
+ }
362
+
363
+ return text
364
+ } catch {
365
+ return ""
366
+ }
367
+ }
368
+
369
+ async function syncNow(
370
+ sessionId,
371
+ flag,
372
+ reason
373
+ ) {
374
+ if (!sessionId) {
375
+ return
376
+ }
94
377
 
95
- try {
96
378
  const args = [
97
- binary,
379
+ TOOLNET_BINARY,
98
380
  "session:opencode-sync",
99
381
  sessionId,
100
382
  "--project",
101
- directory,
383
+ projectRoot,
102
384
  ]
103
385
 
104
386
  if (flag) {
105
- args.push(flag)
387
+ args.push(
388
+ flag
389
+ )
106
390
  }
107
391
 
108
- const child = Bun.spawn(args, {
109
- stdin: "ignore",
110
- stdout: "ignore",
111
- stderr: "ignore",
112
- })
392
+ try {
393
+ await runWithTimeout(
394
+ args
395
+ )
113
396
 
114
- if (
115
- typeof child.unref === "function"
116
- ) {
117
- child.unref()
397
+ writeStatus({
398
+ active: true,
399
+ projectRoot,
400
+ sessionId,
401
+ reason,
402
+ state:
403
+ "sync-success",
404
+ })
405
+ } catch (error) {
406
+ writeStatus({
407
+ active: true,
408
+ projectRoot,
409
+ sessionId,
410
+ reason,
411
+ state:
412
+ "sync-failed",
413
+ error:
414
+ error instanceof
415
+ Error
416
+ ? error.message
417
+ : String(
418
+ error
419
+ ),
420
+ })
421
+
422
+ throw error
118
423
  }
119
- } catch {
120
- // Memory capture must never break OpenCode.
121
424
  }
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)
134
425
 
426
+ function queueSync(
427
+ sessionId,
428
+ flag,
429
+ reason
430
+ ) {
135
431
  if (!sessionId) {
136
- return
432
+ return syncChain
137
433
  }
138
434
 
139
- if (event.type === "session.idle") {
140
- sync(sessionId, "--idle")
141
- return
142
- }
435
+ lastSessionId =
436
+ sessionId
143
437
 
144
- if (event.type === "session.compacted") {
145
- sync(sessionId, "--compacted")
146
- return
147
- }
438
+ syncChain =
439
+ syncChain
440
+ .catch(
441
+ () =>
442
+ undefined
443
+ )
444
+ .then(
445
+ () =>
446
+ syncNow(
447
+ sessionId,
448
+ flag,
449
+ reason
450
+ )
451
+ )
148
452
 
149
- if (event.type === "session.error") {
150
- sync(sessionId, "--error")
151
- }
152
- },
453
+ return syncChain
454
+ }
153
455
 
154
456
  /*
155
- * Official OpenCode compaction hook.
156
- * Preserve ToolNet's semantic state after context compaction.
457
+ * Periodic durability checkpoint.
458
+ *
459
+ * This is intentionally independent
460
+ * from session.idle/dispose because
461
+ * abrupt SSH/process termination may
462
+ * skip shutdown hooks.
463
+ *
464
+ * unref() prevents the timer itself
465
+ * from keeping OpenCode alive.
157
466
  */
158
- "experimental.session.compacting": async (_input, output) => {
159
- const context =
160
- await readContext()
467
+ const periodic =
468
+ setInterval(
469
+ () => {
470
+ if (
471
+ lastSessionId
472
+ ) {
473
+ void queueSync(
474
+ lastSessionId,
475
+ null,
476
+ "periodic"
477
+ )
478
+ }
479
+ },
480
+ PERIODIC_SYNC_MS
481
+ )
161
482
 
162
- if (
163
- context &&
164
- Array.isArray(
165
- output.context
166
- )
167
- ) {
168
- output.context.push(
169
- context
483
+ if (
484
+ typeof periodic.unref ===
485
+ "function"
486
+ ) {
487
+ periodic.unref()
488
+ }
489
+
490
+ return {
491
+ event: async ({
492
+ event,
493
+ }) => {
494
+ const sessionId =
495
+ getSessionId(
496
+ event
497
+ )
498
+
499
+ if (sessionId) {
500
+ lastSessionId =
501
+ sessionId
502
+ }
503
+
504
+ if (
505
+ event.type ===
506
+ "session.idle"
507
+ ) {
508
+ await queueSync(
509
+ sessionId,
510
+ "--idle",
511
+ "session.idle"
512
+ )
513
+
514
+ return
515
+ }
516
+
517
+ if (
518
+ event.type ===
519
+ "session.compacted"
520
+ ) {
521
+ await queueSync(
522
+ sessionId,
523
+ "--compacted",
524
+ "session.compacted"
525
+ )
526
+
527
+ contextCache = {
528
+ value: "",
529
+ expiresAt: 0,
530
+ }
531
+
532
+ return
533
+ }
534
+
535
+ if (
536
+ event.type ===
537
+ "session.error"
538
+ ) {
539
+ await queueSync(
540
+ sessionId,
541
+ "--error",
542
+ "session.error"
543
+ )
544
+
545
+ return
546
+ }
547
+ },
548
+
549
+ /*
550
+ * VERIFIED on OpenCode 1.18.14:
551
+ * this hook fires on real model turns.
552
+ *
553
+ * It remains experimental, therefore
554
+ * AGENTS.md + MCP stay as fallbacks.
555
+ */
556
+ "experimental.chat.system.transform":
557
+ async (
558
+ _input,
559
+ output
560
+ ) => {
561
+ const context =
562
+ await readContext()
563
+
564
+ if (!context) {
565
+ return
566
+ }
567
+
568
+ if (
569
+ Array.isArray(
570
+ output?.system
571
+ )
572
+ ) {
573
+ output.system.push(
574
+ context
575
+ )
576
+
577
+ return
578
+ }
579
+
580
+ if (
581
+ typeof output?.system ===
582
+ "string"
583
+ ) {
584
+ output.system =
585
+ output.system
586
+ ? \`\${output.system}\\n\\n\${context}\`
587
+ : context
588
+ }
589
+ },
590
+
591
+ /*
592
+ * Optional compaction survival hook.
593
+ * We keep it as a supplementary path,
594
+ * never as the only continuity path.
595
+ */
596
+ "experimental.session.compacting":
597
+ async (
598
+ _input,
599
+ output
600
+ ) => {
601
+ const context =
602
+ await readContext()
603
+
604
+ if (
605
+ context &&
606
+ Array.isArray(
607
+ output?.context
608
+ )
609
+ ) {
610
+ output.context.push(
611
+ context
612
+ )
613
+ }
614
+ },
615
+
616
+ /*
617
+ * VERIFIED for clean OpenCode exits.
618
+ * Best effort only: kill -9 / crash /
619
+ * machine loss cannot guarantee this.
620
+ */
621
+ dispose: async () => {
622
+ clearInterval(
623
+ periodic
170
624
  )
171
- }
172
- },
625
+
626
+ if (
627
+ lastSessionId
628
+ ) {
629
+ try {
630
+ await queueSync(
631
+ lastSessionId,
632
+ "--idle",
633
+ "dispose"
634
+ )
635
+ } catch {
636
+ // Status already recorded.
637
+ }
638
+ }
639
+
640
+ try {
641
+ await syncChain
642
+ } catch {
643
+ // Do not block OpenCode shutdown.
644
+ }
645
+ },
646
+ }
173
647
  }
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(`
648
+ `;return T(e,n.trimStart(),{encoding:"utf8",mode:384}),e}import{existsSync as X,mkdirSync as w,readFileSync as q,writeFileSync as k}from"node:fs";import{homedir as E}from"node:os";import{dirname as j,join as y}from"node:path";function U(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 b(o={}){let t=o.configFile??y(process.env.CODEX_HOME??y(E(),".codex"),"config.toml"),e=o.previousFile??y(E(),".config","toolnet-memory","codex-notify-previous.json");w(j(t),{recursive:!0}),w(j(e),{recursive:!0});let r=X(t)?q(t,"utf8"):"",n=o.binary??"toolnet-memory",a=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
176
649
  `),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)+`
650
+ `);l=U(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 v=l.length>=2&&l[l.length-1]==="session:codex-notify";return l.length>0&&!v&&k(e,JSON.stringify(l,null,2)+`
178
651
  `,{encoding:"utf8",mode:384}),r=s.join(`
179
652
  `),r.endsWith(`
180
653
  `)||(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};
654
+ `),k(t,r,{encoding:"utf8",mode:384}),{configFile:t,previousFile:e,preservedPrevious:l.length>0&&!v}}import{existsSync as W,mkdirSync as G,readFileSync as K,writeFileSync as V}from"node:fs";import{homedir as z}from"node:os";import{dirname as Q,join as A}from"node:path";function Z(o){return`'${o.replace(/'/g,"'\\''")}'`}function R(o={}){let t=o.hooksFile??A(process.env.CODEX_HOME??A(z(),".codex"),"hooks.json");G(Q(t),{recursive:!0});let e={};if(W(t))try{e=JSON.parse(K(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,V(t,JSON.stringify(e,null,2)+`
655
+ `,{encoding:"utf8",mode:384}),t}function S(o){return ee("sh",["-lc",`command -v ${JSON.stringify(o)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function te(){return S("agy")||g(x(h(),".gemini"))}function oe(){return S("opencode")||g(x(h(),".config","opencode"))}function ne(){return S("codex")||g(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=O({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=N({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=b({binary:t}),a=R({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};