myagentmemory 0.4.12 → 0.4.14

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.
@@ -0,0 +1,501 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import {
5
+ COMMAND_DESCRIPTIONS,
6
+ COMMAND_OPTIONS,
7
+ COMMANDS,
8
+ GLOBAL_OPTIONS,
9
+ OPTION_SPECS,
10
+ optionDescription,
11
+ PLUGIN_COMMAND_DESCRIPTIONS,
12
+ PLUGIN_COMMAND_OPTIONS,
13
+ PLUGIN_COMMANDS,
14
+ SCRATCHPAD_ACTION_DESCRIPTIONS,
15
+ SCRATCHPAD_ACTION_OPTIONS,
16
+ SCRATCHPAD_ACTIONS,
17
+ SHELL_DESCRIPTIONS,
18
+ WORKER_ACTION_DESCRIPTIONS,
19
+ WORKER_ACTION_OPTIONS,
20
+ WORKER_ACTIONS,
21
+ } from "./cli-spec.js";
22
+
23
+ export type CompletionShell = "bash" | "zsh" | "fish" | "powershell";
24
+
25
+ export interface CompletionInstallResult {
26
+ shell: CompletionShell;
27
+ completionPath: string;
28
+ profilePath?: string;
29
+ profileUpdated: boolean;
30
+ }
31
+
32
+ function words(values: readonly string[]): string {
33
+ return values.join(" ");
34
+ }
35
+
36
+ function zshOptionValue(command: string, option: string): string {
37
+ if (option === "--target") {
38
+ return command === "read"
39
+ ? ":target:(daily long_term scratchpad list topic topics)"
40
+ : ":target:(daily long_term topic)";
41
+ }
42
+ if (option === "--mode") return command === "search" ? ":mode:(keyword semantic deep)" : ":mode:(append overwrite)";
43
+ if (option === "--scope") return ":scope:(global current)";
44
+ if (option === "--host") return command === "web" ? ":host:(127.0.0.1 localhost ::1)" : ":host:(pi codex claude)";
45
+ if (option === "--only") return ":agent:(claude codex cursor opencode pi)";
46
+ const value = OPTION_SPECS[option]?.value;
47
+ if (value?.kind === "directory") return `:${value.label}:_directories`;
48
+ if (value?.kind === "file") return `:${value.label}:_files`;
49
+ if (value) return `:${value.label}:`;
50
+ return "";
51
+ }
52
+
53
+ function zshOptionSpecs(command: string, options: readonly string[] = COMMAND_OPTIONS[command] ?? []): string {
54
+ return options
55
+ .map((option) => `'${option}[${optionDescription(option)}]${zshOptionValue(command, option)}'`)
56
+ .join(" ");
57
+ }
58
+
59
+ function fishOption(command: string, condition: string, option: string): string {
60
+ const spec = OPTION_SPECS[option];
61
+ const value = spec?.value ? " -r" : "";
62
+ let suggestions = "";
63
+ if (option === "--target")
64
+ suggestions =
65
+ command === "read" ? " -a 'daily long_term scratchpad list topic topics'" : " -a 'daily long_term topic'";
66
+ else if (option === "--mode")
67
+ suggestions = command === "search" ? " -a 'keyword semantic deep'" : " -a 'append overwrite'";
68
+ else if (option === "--scope") suggestions = " -a 'global current'";
69
+ else if (option === "--host")
70
+ suggestions = command === "web" ? " -a '127.0.0.1 localhost ::1'" : " -a 'pi codex claude'";
71
+ else if (option === "--only") suggestions = " -a 'claude codex cursor opencode pi'";
72
+ else if (spec?.value?.kind === "directory") suggestions = " -a '(__fish_complete_directories)'";
73
+ else if (spec?.value?.kind === "file") suggestions = " -F";
74
+ return `complete -c agent-memory -n '${condition}' -l ${option.slice(2)}${value}${suggestions} -d '${optionDescription(option)}'`;
75
+ }
76
+
77
+ function bashCompletion(): string {
78
+ return `# agent-memory completion for Bash
79
+ # Installed automatically by: agent-memory completion bash
80
+ # Print this script instead with: agent-memory completion bash --stdout
81
+ _agent_memory_completion() {
82
+ local cur command sub action
83
+ COMPREPLY=()
84
+ cur="\${COMP_WORDS[COMP_CWORD]}"
85
+ command="\${COMP_WORDS[1]}"
86
+ sub="\${COMP_WORDS[2]}"
87
+ action="\${COMP_WORDS[3]}"
88
+
89
+ if [[ "$cur" == --*=* ]]; then return 0; fi
90
+ case "\${COMP_WORDS[COMP_CWORD-1]}" in
91
+ --target)
92
+ if [[ "$command" == read ]]; then COMPREPLY=( $(compgen -W "daily long_term scratchpad list topic topics" -- "$cur") );
93
+ else COMPREPLY=( $(compgen -W "daily long_term topic" -- "$cur") ); fi; return 0 ;;
94
+ --mode)
95
+ if [[ "$command" == search ]]; then COMPREPLY=( $(compgen -W "keyword semantic deep" -- "$cur") );
96
+ else COMPREPLY=( $(compgen -W "append overwrite" -- "$cur") ); fi; return 0 ;;
97
+ --scope) COMPREPLY=( $(compgen -W "global current" -- "$cur") ); return 0 ;;
98
+ --host)
99
+ if [[ "$command" == web ]]; then COMPREPLY=( $(compgen -W "127.0.0.1 localhost ::1" -- "$cur") );
100
+ else COMPREPLY=( $(compgen -W "pi codex claude" -- "$cur") ); fi; return 0 ;;
101
+ --only) COMPREPLY=( $(compgen -W "claude codex cursor opencode pi" -- "$cur") ); return 0 ;;
102
+ --dir|--cwd|--pi|--codex|--claude|--state|--journal|--candidates-dir|--decisions-dir|--auto-dir|--output)
103
+ COMPREPLY=( $(compgen -f -- "$cur") ); return 0 ;;
104
+ esac
105
+
106
+ if (( COMP_CWORD == 1 )); then
107
+ COMPREPLY=( $(compgen -W "${words(COMMANDS)}" -- "$cur") ); return 0
108
+ fi
109
+ if [[ "$command" == plugin && $COMP_CWORD -eq 2 ]]; then
110
+ COMPREPLY=( $(compgen -W "${words(PLUGIN_COMMANDS)}" -- "$cur") ); return 0
111
+ fi
112
+ if [[ "$command" == plugin && "$sub" == worker && $COMP_CWORD -eq 3 ]]; then
113
+ COMPREPLY=( $(compgen -W "${words(WORKER_ACTIONS)}" -- "$cur") ); return 0
114
+ fi
115
+ if [[ "$command" == scratchpad && $COMP_CWORD -eq 2 ]]; then
116
+ COMPREPLY=( $(compgen -W "${words(SCRATCHPAD_ACTIONS)}" -- "$cur") ); return 0
117
+ fi
118
+ if [[ "$command" == completion && $COMP_CWORD -eq 2 ]]; then
119
+ COMPREPLY=( $(compgen -W "bash zsh fish powershell" -- "$cur") ); return 0
120
+ fi
121
+ local command_options=""
122
+ if [[ "$command" == plugin ]]; then
123
+ case "$sub" in
124
+ ${Object.entries(PLUGIN_COMMAND_OPTIONS)
125
+ .map(([command, options]) => ` ${command}) command_options="${words(options)}" ;;`)
126
+ .join("\n")}
127
+ esac
128
+ if [[ "$sub" == worker && -n "$action" ]]; then
129
+ case "$action" in
130
+ ${Object.entries(WORKER_ACTION_OPTIONS)
131
+ .map(([action, options]) => ` ${action}) command_options="${words(options)}" ;;`)
132
+ .join("\n")}
133
+ esac
134
+ fi
135
+ elif [[ "$command" == scratchpad && -n "$sub" ]]; then
136
+ case "$sub" in
137
+ ${Object.entries(SCRATCHPAD_ACTION_OPTIONS)
138
+ .map(([action, options]) => ` ${action}) command_options="${words(options)}" ;;`)
139
+ .join("\n")}
140
+ esac
141
+ else
142
+ case "$command" in
143
+ ${Object.entries(COMMAND_OPTIONS)
144
+ .map(([command, options]) => ` ${command}) command_options="${words(options)}" ;;`)
145
+ .join("\n")}
146
+ esac
147
+ fi
148
+ COMPREPLY=( $(compgen -W "${words(GLOBAL_OPTIONS)} $command_options" -- "$cur") )
149
+ }
150
+ complete -F _agent_memory_completion agent-memory
151
+ `;
152
+ }
153
+
154
+ function zshCompletion(): string {
155
+ return `#compdef agent-memory
156
+ # agent-memory completion for Zsh
157
+ # Installed automatically by: agent-memory completion zsh
158
+ # Print this script instead with: agent-memory completion zsh --stdout
159
+ _agent-memory() {
160
+ local -a commands plugin_commands worker_actions scratchpad_actions shells
161
+ local command="$words[2]"
162
+ local subcommand="$words[3]"
163
+ local action="$words[4]"
164
+ local command_position=$CURRENT
165
+ commands=(${COMMANDS.map((command) => `'${command}:${COMMAND_DESCRIPTIONS[command] ?? command}'`).join(" ")})
166
+ plugin_commands=(${PLUGIN_COMMANDS.map((command) => `'${command}:${PLUGIN_COMMAND_DESCRIPTIONS[command]}'`).join(" ")})
167
+ worker_actions=(${WORKER_ACTIONS.map((action) => `'${action}:${WORKER_ACTION_DESCRIPTIONS[action]}'`).join(" ")})
168
+ scratchpad_actions=(${SCRATCHPAD_ACTIONS.map((action) => `'${action}:${SCRATCHPAD_ACTION_DESCRIPTIONS[action]}'`).join(" ")})
169
+ shells=(${Object.entries(SHELL_DESCRIPTIONS)
170
+ .map(([shell, description]) => `'${shell}:${description}'`)
171
+ .join(" ")})
172
+
173
+ _arguments -C \\
174
+ '(-h --help)'{-h,--help}'[show help]' \\
175
+ '(-V --version)'{-V,--version}'[show version]' \\
176
+ '--json[emit structured JSON]' \\
177
+ '--dir[memory directory]:directory:_directories' \\
178
+ '1:command:->command' \\
179
+ '*::argument:->args'
180
+
181
+ case $state in
182
+ command) _describe 'command' commands ;;
183
+ args)
184
+ case $command in
185
+ plugin)
186
+ if (( command_position == 3 )); then _describe 'plugin command' plugin_commands
187
+ elif [[ "$subcommand" == worker ]] && (( command_position == 4 )); then _describe 'worker action' worker_actions
188
+ else
189
+ if [[ "$subcommand" == worker && -n "$action" ]]; then
190
+ case "$action" in
191
+ ${Object.entries(WORKER_ACTION_OPTIONS)
192
+ .map(
193
+ ([action, options]) =>
194
+ ` ${action}) _arguments ${zshOptionSpecs(`plugin:worker:${action}`, options)} ;;`,
195
+ )
196
+ .join("\n")}
197
+ esac
198
+ else
199
+ case "$subcommand" in
200
+ ${Object.entries(PLUGIN_COMMAND_OPTIONS)
201
+ .map(
202
+ ([command, options]) => ` ${command}) _arguments ${zshOptionSpecs(`plugin:${command}`, options)} ;;`,
203
+ )
204
+ .join("\n")}
205
+ esac
206
+ fi
207
+ fi ;;
208
+ scratchpad)
209
+ if (( command_position == 3 )); then _describe 'action' scratchpad_actions
210
+ else
211
+ case "$subcommand" in
212
+ ${Object.entries(SCRATCHPAD_ACTION_OPTIONS)
213
+ .map(([action, options]) => ` ${action}) _arguments ${zshOptionSpecs("scratchpad", options)} ;;`)
214
+ .join("\n")}
215
+ esac
216
+ fi ;;
217
+ completion)
218
+ if (( command_position == 3 )); then _describe 'shell' shells
219
+ else _arguments ${zshOptionSpecs("completion")}; fi ;;
220
+ ${Object.keys(COMMAND_OPTIONS)
221
+ .filter((command) => command !== "plugin" && command !== "scratchpad" && command !== "completion")
222
+ .map((command) => ` ${command}) _arguments ${zshOptionSpecs(command)} ;;`)
223
+ .join("\n")}
224
+ *) _arguments ;;
225
+ esac ;;
226
+ esac
227
+ }
228
+ compdef _agent-memory agent-memory
229
+ `;
230
+ }
231
+
232
+ function fishCompletion(): string {
233
+ const lines = [
234
+ "# agent-memory completion for Fish",
235
+ "# Installed automatically by: agent-memory completion fish",
236
+ "# Print this script instead with: agent-memory completion fish --stdout",
237
+ "complete -c agent-memory -f",
238
+ ...COMMANDS.map(
239
+ (command) =>
240
+ `complete -c agent-memory -n '__fish_use_subcommand' -a '${command}' -d '${COMMAND_DESCRIPTIONS[command] ?? command}'`,
241
+ ),
242
+ ...PLUGIN_COMMANDS.map(
243
+ (command) =>
244
+ `complete -c agent-memory -n '__fish_seen_subcommand_from plugin; and not __fish_seen_subcommand_from ${words(PLUGIN_COMMANDS)}' -a '${command}' -d '${PLUGIN_COMMAND_DESCRIPTIONS[command]}'`,
245
+ ),
246
+ ...WORKER_ACTIONS.map(
247
+ (action) =>
248
+ `complete -c agent-memory -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from worker; and not __fish_seen_subcommand_from ${words(WORKER_ACTIONS)}' -a '${action}' -d '${WORKER_ACTION_DESCRIPTIONS[action]}'`,
249
+ ),
250
+ ...SCRATCHPAD_ACTIONS.map(
251
+ (action) =>
252
+ `complete -c agent-memory -n '__fish_seen_subcommand_from scratchpad; and not __fish_seen_subcommand_from ${words(SCRATCHPAD_ACTIONS)}' -a '${action}' -d '${SCRATCHPAD_ACTION_DESCRIPTIONS[action]}'`,
253
+ ),
254
+ ...Object.entries(SHELL_DESCRIPTIONS).map(
255
+ ([shell, description]) =>
256
+ `complete -c agent-memory -n '__fish_seen_subcommand_from completion' -a '${shell}' -d '${description}'`,
257
+ ),
258
+ "complete -c agent-memory -l dir -r -a '(__fish_complete_directories)' -d 'override the active memory directory'",
259
+ "complete -c agent-memory -l json -d 'emit command-specific structured JSON'",
260
+ "complete -c agent-memory -s h -l help -d 'show help for the selected command'",
261
+ "complete -c agent-memory -s V -l version -d 'print the installed version and exit'",
262
+ ];
263
+ for (const [command, options] of Object.entries(COMMAND_OPTIONS)) {
264
+ if (command === "scratchpad") continue;
265
+ for (const option of options) {
266
+ lines.push(fishOption(command, `__fish_seen_subcommand_from ${command}`, option));
267
+ }
268
+ }
269
+ for (const [subcommand, options] of Object.entries(PLUGIN_COMMAND_OPTIONS)) {
270
+ if (subcommand === "worker") continue;
271
+ for (const option of options) {
272
+ lines.push(
273
+ fishOption(
274
+ `plugin:${subcommand}`,
275
+ `__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from ${subcommand}`,
276
+ option,
277
+ ),
278
+ );
279
+ }
280
+ }
281
+ for (const [action, options] of Object.entries(WORKER_ACTION_OPTIONS)) {
282
+ for (const option of options) {
283
+ lines.push(
284
+ fishOption(
285
+ `plugin:worker:${action}`,
286
+ `__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from worker; and __fish_seen_subcommand_from ${action}`,
287
+ option,
288
+ ),
289
+ );
290
+ }
291
+ }
292
+ for (const [action, options] of Object.entries(SCRATCHPAD_ACTION_OPTIONS)) {
293
+ for (const option of options) {
294
+ lines.push(
295
+ fishOption(
296
+ "scratchpad",
297
+ `__fish_seen_subcommand_from scratchpad; and __fish_seen_subcommand_from ${action}`,
298
+ option,
299
+ ),
300
+ );
301
+ }
302
+ }
303
+ return `${lines.join("\n")}\n`;
304
+ }
305
+
306
+ function powershellCompletion(): string {
307
+ const psOptions = (options: readonly string[]) => (options.length ? `@('${options.join("','")}')` : "@()");
308
+ const optionCases = Object.entries(COMMAND_OPTIONS)
309
+ .map(([command, options]) => ` '${command}' { $candidates += ${psOptions(options)} }`)
310
+ .join("\n");
311
+ const pluginOptionCases = Object.entries(PLUGIN_COMMAND_OPTIONS)
312
+ .map(([command, options]) => ` '${command}' { $candidates += ${psOptions(options)} }`)
313
+ .join("\n");
314
+ const workerOptionCases = Object.entries(WORKER_ACTION_OPTIONS)
315
+ .map(([action, options]) => ` '${action}' { $candidates += ${psOptions(options)} }`)
316
+ .join("\n");
317
+ const scratchpadOptionCases = Object.entries(SCRATCHPAD_ACTION_OPTIONS)
318
+ .map(([action, options]) => ` '${action}' { $candidates += ${psOptions(options)} }`)
319
+ .join("\n");
320
+ return `# agent-memory completion for PowerShell
321
+ # Installed automatically by: agent-memory completion powershell
322
+ # Print this script instead with: agent-memory completion powershell --stdout
323
+ Register-ArgumentCompleter -Native -CommandName agent-memory -ScriptBlock {
324
+ param($wordToComplete, $commandAst, $cursorPosition)
325
+ $tokens = @($commandAst.CommandElements | ForEach-Object { $_.ToString() })
326
+ $commands = @('${COMMANDS.join("','")}')
327
+ $pluginCommands = @('${PLUGIN_COMMANDS.join("','")}')
328
+ $workerActions = @('${WORKER_ACTIONS.join("','")}')
329
+ $scratchpadActions = @('${SCRATCHPAD_ACTIONS.join("','")}')
330
+ $shells = @('bash','zsh','fish','powershell')
331
+ $commandDescriptions = @{
332
+ ${Object.entries(COMMAND_DESCRIPTIONS)
333
+ .map(([command, description]) => ` '${command}' = '${description}'`)
334
+ .join("\n")}
335
+ }
336
+ $pluginCommandDescriptions = @{
337
+ ${Object.entries(PLUGIN_COMMAND_DESCRIPTIONS)
338
+ .map(([command, description]) => ` '${command}' = '${description}'`)
339
+ .join("\n")}
340
+ }
341
+ $workerActionDescriptions = @{
342
+ ${Object.entries(WORKER_ACTION_DESCRIPTIONS)
343
+ .map(([action, description]) => ` '${action}' = '${description}'`)
344
+ .join("\n")}
345
+ }
346
+ $scratchpadActionDescriptions = @{
347
+ ${Object.entries(SCRATCHPAD_ACTION_DESCRIPTIONS)
348
+ .map(([action, description]) => ` '${action}' = '${description}'`)
349
+ .join("\n")}
350
+ }
351
+ $shellDescriptions = @{
352
+ ${Object.entries(SHELL_DESCRIPTIONS)
353
+ .map(([shell, description]) => ` '${shell}' = '${description}'`)
354
+ .join("\n")}
355
+ }
356
+ $optionDescriptions = @{
357
+ ${Object.keys(OPTION_SPECS)
358
+ .map((option) => ` '${option}' = '${optionDescription(option)}'`)
359
+ .join("\n")}
360
+ }
361
+ $globalOptions = @('${GLOBAL_OPTIONS.join("','")}')
362
+ $command = if ($tokens.Count -gt 1) { $tokens[1] } else { '' }
363
+ $subcommand = if ($tokens.Count -gt 2) { $tokens[2] } else { '' }
364
+ $action = if ($tokens.Count -gt 3) { $tokens[3] } else { '' }
365
+ $candidates = @()
366
+ $candidateDescriptions = @{}
367
+
368
+ if ($tokens.Count -le 2) { $candidates = $commands + $globalOptions; $candidateDescriptions = $commandDescriptions }
369
+ elseif ($command -eq 'plugin' -and $tokens.Count -le 4 -and $tokens[2] -eq 'worker') { $candidates = $workerActions + $globalOptions; $candidateDescriptions = $workerActionDescriptions }
370
+ elseif ($command -eq 'plugin' -and $tokens.Count -le 3) { $candidates = $pluginCommands + $globalOptions; $candidateDescriptions = $pluginCommandDescriptions }
371
+ elseif ($command -eq 'scratchpad' -and $tokens.Count -le 3) { $candidates = $scratchpadActions + $globalOptions; $candidateDescriptions = $scratchpadActionDescriptions }
372
+ elseif ($command -eq 'completion' -and $tokens.Count -le 3) { $candidates = $shells; $candidateDescriptions = $shellDescriptions }
373
+ else {
374
+ $candidates = $globalOptions
375
+ $candidateDescriptions = $optionDescriptions
376
+ if ($command -eq 'plugin') {
377
+ if ($subcommand -eq 'worker' -and $action) {
378
+ switch ($action) {
379
+ ${workerOptionCases}
380
+ }
381
+ } else {
382
+ switch ($subcommand) {
383
+ ${pluginOptionCases}
384
+ }
385
+ }
386
+ } elseif ($command -eq 'scratchpad') {
387
+ switch ($subcommand) {
388
+ ${scratchpadOptionCases}
389
+ }
390
+ } else {
391
+ switch ($command) {
392
+ ${optionCases}
393
+ }
394
+ }
395
+ }
396
+
397
+ $candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
398
+ $description = if ($candidateDescriptions.ContainsKey($_)) { $candidateDescriptions[$_] } elseif ($optionDescriptions.ContainsKey($_)) { $optionDescriptions[$_] } else { $_ }
399
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $description)
400
+ }
401
+ }
402
+ `;
403
+ }
404
+
405
+ export function generateCompletion(shell: CompletionShell): string {
406
+ switch (shell) {
407
+ case "bash":
408
+ return bashCompletion();
409
+ case "zsh":
410
+ return zshCompletion();
411
+ case "fish":
412
+ return fishCompletion();
413
+ case "powershell":
414
+ return powershellCompletion();
415
+ }
416
+ }
417
+
418
+ export function detectCompletionShell(
419
+ environment: Record<string, string | undefined> = process.env,
420
+ platform = process.platform,
421
+ ): CompletionShell | null {
422
+ const shell = path.basename(environment.SHELL ?? "").toLowerCase();
423
+ if (shell === "bash" || shell === "zsh" || shell === "fish") return shell;
424
+ if (shell.includes("pwsh") || shell.includes("powershell")) return "powershell";
425
+ if (platform === "win32" && environment.PSModulePath) return "powershell";
426
+ return null;
427
+ }
428
+
429
+ function writeCompletionFile(filePath: string, content: string): void {
430
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o755 });
431
+ fs.writeFileSync(filePath, content, { mode: 0o644 });
432
+ }
433
+
434
+ function ensureProfileBlock(filePath: string, lines: string[]): boolean {
435
+ const start = "# >>> agent-memory completion >>>";
436
+ const end = "# <<< agent-memory completion <<<";
437
+ const block = `${start}\n${lines.join("\n")}\n${end}`;
438
+ const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
439
+ const startIndex = current.indexOf(start);
440
+ const endIndex = startIndex === -1 ? -1 : current.indexOf(end, startIndex);
441
+ let updated: string;
442
+
443
+ if (startIndex !== -1 && endIndex !== -1) {
444
+ updated = `${current.slice(0, startIndex)}${block}${current.slice(endIndex + end.length)}`;
445
+ } else {
446
+ const separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
447
+ updated = `${current}${separator}${block}\n`;
448
+ }
449
+ if (updated === current) return false;
450
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o755 });
451
+ fs.writeFileSync(filePath, updated, { mode: 0o600 });
452
+ return true;
453
+ }
454
+
455
+ export function installCompletion(
456
+ shell: CompletionShell,
457
+ options: { homeDir?: string; platform?: NodeJS.Platform } = {},
458
+ ): CompletionInstallResult {
459
+ const homeDir = options.homeDir ?? os.homedir();
460
+ const platform = options.platform ?? process.platform;
461
+ const completionDir = path.join(homeDir, ".config", "agent-memory", "completions");
462
+
463
+ if (shell === "fish") {
464
+ const completionPath = path.join(homeDir, ".config", "fish", "completions", "agent-memory.fish");
465
+ writeCompletionFile(completionPath, generateCompletion(shell));
466
+ return { shell, completionPath, profileUpdated: false };
467
+ }
468
+
469
+ const extension = shell === "powershell" ? "ps1" : shell;
470
+ const completionPath = path.join(completionDir, `agent-memory.${extension}`);
471
+ writeCompletionFile(completionPath, generateCompletion(shell));
472
+
473
+ if (shell === "bash") {
474
+ const profilePath = path.join(homeDir, ".bashrc");
475
+ const profileUpdated = ensureProfileBlock(profilePath, [
476
+ `if [[ -r "$HOME/.config/agent-memory/completions/agent-memory.bash" ]]; then`,
477
+ ` source "$HOME/.config/agent-memory/completions/agent-memory.bash"`,
478
+ "fi",
479
+ ]);
480
+ return { shell, completionPath, profilePath, profileUpdated };
481
+ }
482
+
483
+ if (shell === "zsh") {
484
+ const profilePath = path.join(homeDir, ".zshrc");
485
+ const profileUpdated = ensureProfileBlock(profilePath, [
486
+ "autoload -Uz compinit",
487
+ "if (( ! $+functions[compdef] )); then compinit; fi",
488
+ `source "$HOME/.config/agent-memory/completions/agent-memory.zsh"`,
489
+ ]);
490
+ return { shell, completionPath, profilePath, profileUpdated };
491
+ }
492
+
493
+ const profilePath =
494
+ platform === "win32"
495
+ ? path.join(homeDir, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
496
+ : path.join(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1");
497
+ const profileUpdated = ensureProfileBlock(profilePath, [
498
+ `. "$HOME/.config/agent-memory/completions/agent-memory.ps1"`,
499
+ ]);
500
+ return { shell, completionPath, profilePath, profileUpdated };
501
+ }