@@ -38,6 +38,7 @@ const AGENTMEMORY_INSTALL_COMMAND = "bun install -g @agentmemory/agentmemory";
3838const AGENTMEMORY_START_COMMAND = "agentmemory" ;
3939const DEFAULT_AGENTMEMORY_PORT = 3111 ;
4040const OMA_AGENTMEMORY_PID_FILE = "oma-agentmemory.pid" ;
41+ const LAUNCHD_AGENTMEMORY_LABEL = "dev.oma.agentmemory" ;
4142
4243type AgentMemoryInstaller = ( ) => Promise < {
4344 status : number | null ;
@@ -135,6 +136,69 @@ function writeOwnedPid(homeDir: string, pid: number): void {
135136 writeFileSync ( pidPath , `${ pid } \n` , { encoding : "utf-8" , mode : 0o600 } ) ;
136137}
137138
139+ function servicePathEnvironment ( homeDir : string ) : string {
140+ return [
141+ join ( homeDir , ".bun" , "bin" ) ,
142+ join ( homeDir , ".local" , "bin" ) ,
143+ "/opt/homebrew/bin" ,
144+ "/usr/local/bin" ,
145+ "/usr/bin" ,
146+ "/bin" ,
147+ "/usr/sbin" ,
148+ "/sbin" ,
149+ ] . join ( ":" ) ;
150+ }
151+
152+ function renderLaunchdService ( args : { homeDir : string ; port : number } ) : string {
153+ return `<?xml version="1.0" encoding="UTF-8"?>
154+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
155+ <plist version="1.0">
156+ <dict>
157+ <key>Label</key>
158+ <string>${ LAUNCHD_AGENTMEMORY_LABEL } </string>
159+ <key>ProgramArguments</key>
160+ <array>
161+ <string>/usr/bin/env</string>
162+ <string>agentmemory</string>
163+ </array>
164+ <key>EnvironmentVariables</key>
165+ <dict>
166+ <key>PATH</key>
167+ <string>${ servicePathEnvironment ( args . homeDir ) } </string>
168+ <key>III_REST_PORT</key>
169+ <string>${ args . port } </string>
170+ </dict>
171+ <key>RunAtLoad</key>
172+ <true/>
173+ <key>KeepAlive</key>
174+ <true/>
175+ <key>StandardOutPath</key>
176+ <string>/tmp/oma-agentmemory.out.log</string>
177+ <key>StandardErrorPath</key>
178+ <string>/tmp/oma-agentmemory.err.log</string>
179+ </dict>
180+ </plist>
181+ ` ;
182+ }
183+
184+ function renderSystemdService ( args : { homeDir : string ; port : number } ) : string {
185+ return `[Unit]
186+ Description=OMA AgentMemory daemon
187+ After=network.target
188+
189+ [Service]
190+ Type=simple
191+ Environment=PATH=${ servicePathEnvironment ( args . homeDir ) }
192+ Environment=III_REST_PORT=${ args . port }
193+ ExecStart=/usr/bin/env agentmemory
194+ Restart=on-failure
195+ RestartSec=2
196+
197+ [Install]
198+ WantedBy=default.target
199+ ` ;
200+ }
201+
138202export async function initMemory (
139203 jsonMode = false ,
140204 forceMode = false ,
@@ -189,6 +253,7 @@ export async function setupAgentMemory(
189253 dryRun ?: boolean ;
190254 install ?: boolean ;
191255 start ?: boolean ;
256+ platform ?: NodeJS . Platform ;
192257 installer ?: AgentMemoryInstaller ;
193258 } = { } ,
194259) : Promise < MemorySetupResult > {
@@ -200,6 +265,7 @@ export async function setupAgentMemory(
200265 let installExitCode : number | null | undefined ;
201266 let installSkipped : boolean | undefined ;
202267 let installError : string | undefined ;
268+ let service : MemoryServiceResult | undefined ;
203269 let daemon : MemoryDaemonResult | undefined ;
204270
205271 const nextConfig : AgentMemoryEndpointConfig | null = args . endpoint
@@ -232,6 +298,14 @@ export async function setupAgentMemory(
232298 ) ;
233299 }
234300 }
301+ if ( args . dryRun || installExitCode === 0 ) {
302+ service = installAgentMemoryService ( {
303+ homeDir,
304+ platform : args . platform ?? process . platform ,
305+ dryRun : args . dryRun ,
306+ port : port ?? DEFAULT_AGENTMEMORY_PORT ,
307+ } ) ;
308+ }
235309 }
236310
237311 if ( args . start ) {
@@ -265,6 +339,7 @@ export async function setupAgentMemory(
265339 installExitCode,
266340 installSkipped,
267341 installError,
342+ service,
268343 startRequested : args . start === true ,
269344 daemon,
270345 installCommand : AGENTMEMORY_INSTALL_COMMAND ,
@@ -431,27 +506,50 @@ export async function controlAgentMemoryDaemon(args: {
431506}
432507
433508export function installAgentMemoryService (
434- args : { homeDir ?: string ; platform ?: NodeJS . Platform ; dryRun ?: boolean } = { } ,
509+ args : {
510+ homeDir ?: string ;
511+ platform ?: NodeJS . Platform ;
512+ dryRun ?: boolean ;
513+ port ?: number | string ;
514+ } = { } ,
435515) : MemoryServiceResult {
436516 const homeDir = args . homeDir ?? homedir ( ) ;
437517 const platform = args . platform ?? process . platform ;
518+ const port = parsePositivePort ( args . port ) ?? DEFAULT_AGENTMEMORY_PORT ;
438519 const servicePath =
439520 platform === "darwin"
440521 ? join ( homeDir , "Library" , "LaunchAgents" , "dev.oma.agentmemory.plist" )
441522 : platform === "linux"
442523 ? join ( homeDir , ".config" , "systemd" , "user" , "oma-agentmemory.service" )
443524 : undefined ;
525+ const content =
526+ platform === "darwin"
527+ ? renderLaunchdService ( { homeDir, port } )
528+ : platform === "linux"
529+ ? renderSystemdService ( { homeDir, port } )
530+ : undefined ;
531+
532+ let wroteFile = false ;
533+ if ( servicePath && content && ! args . dryRun ) {
534+ mkdirSync ( dirname ( servicePath ) , { recursive : true , mode : 0o700 } ) ;
535+ writeFileSync ( servicePath , content , { encoding : "utf-8" , mode : 0o600 } ) ;
536+ wroteFile = true ;
537+ }
444538
445539 return {
446540 action : "install" ,
447541 platform,
448542 supported : servicePath !== undefined ,
449543 dryRun : args . dryRun === true ,
450544 servicePath,
545+ wroteFile,
546+ content : args . dryRun ? content : undefined ,
451547 message :
452548 servicePath === undefined
453549 ? `AgentMemory service install is not supported on ${ platform } `
454- : "AgentMemory service file generation is deferred to PR6; use memory:daemon start for now" ,
550+ : args . dryRun
551+ ? "AgentMemory service file would be written"
552+ : "AgentMemory service file installed" ,
455553 } ;
456554}
457555
@@ -598,6 +696,12 @@ export async function printAgentMemorySetup(
598696 : pc . red ( String ( result . installExitCode ?? "unknown" ) ) ;
599697 lines . push ( `Install result: ${ installState } ` ) ;
600698 }
699+ if ( result . service ?. servicePath ) {
700+ lines . push ( `Service: ${ pc . cyan ( result . service . servicePath ) } ` ) ;
701+ lines . push (
702+ `Service file: ${ result . service . wroteFile ? pc . green ( "installed" ) : pc . yellow ( result . service . dryRun ? "preview" : "not written" ) } ` ,
703+ ) ;
704+ }
601705 lines . push ( `Start: ${ pc . cyan ( result . startCommand ) } ` ) ;
602706 if ( result . daemon ?. startedPid )
603707 lines . push ( `Started PID: ${ result . daemon . startedPid } ` ) ;
@@ -645,8 +749,9 @@ export async function printAgentMemoryDaemon(
645749export function printAgentMemoryServiceInstall (
646750 jsonMode = false ,
647751 dryRun = false ,
752+ port ?: number | string ,
648753) : void {
649- const result = installAgentMemoryService ( { dryRun } ) ;
754+ const result = installAgentMemoryService ( { dryRun, port } ) ;
650755 if ( jsonMode ) {
651756 console . log ( JSON . stringify ( result , null , 2 ) ) ;
652757 return ;
@@ -656,7 +761,9 @@ export function printAgentMemoryServiceInstall(
656761 [
657762 `Platform: ${ result . platform } ` ,
658763 result . servicePath ? `Path: ${ pc . cyan ( result . servicePath ) } ` : null ,
764+ `Wrote file: ${ result . wroteFile ? pc . green ( "yes" ) : "no" } ` ,
659765 result . supported ? pc . yellow ( result . message ) : pc . red ( result . message ) ,
766+ result . content ? `\n${ result . content . trimEnd ( ) } ` : null ,
660767 ]
661768 . filter ( ( line ) : line is string => line !== null )
662769 . join ( "\n" ) ,
0 commit comments