toon-memory 1.5.0 ā 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -51
- package/bin/toon-memory.js +0 -0
- package/dist/cli/setup.js +114 -21
- package/mcp/server.js +11 -11
- package/package.json +1 -1
- package/src/cli/setup.ts +306 -34
- package/src/mcp/server.ts +204 -13
package/src/cli/setup.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs"
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync, readdirSync, statSync } from "fs"
|
|
2
2
|
import { basename, dirname, join } from "path"
|
|
3
3
|
import { fileURLToPath } from "url"
|
|
4
4
|
import { execSync } from "child_process"
|
|
5
5
|
import { createInterface } from "readline"
|
|
6
6
|
import { createRequire } from "module"
|
|
7
|
+
import { gzipSync, gunzipSync } from "zlib"
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
10
|
const projectRoot = process.cwd()
|
|
@@ -12,10 +13,15 @@ const projectRoot = process.cwd()
|
|
|
12
13
|
const sourceDir = join(__dirname, "..", "..", "src")
|
|
13
14
|
const HOME = process.env.HOME || process.env.USERPROFILE || "~"
|
|
14
15
|
|
|
16
|
+
/** Supported AI coding agent configuration */
|
|
15
17
|
interface Agent {
|
|
18
|
+
/** Agent identifier (e.g., "opencode", "vscode/copilot") */
|
|
16
19
|
name: string
|
|
20
|
+
/** Global config file path (e.g., ~/.config/opencode/opencode.json) */
|
|
17
21
|
global?: string
|
|
22
|
+
/** Local (project-level) config file path (e.g., .opencode/opencode.json) */
|
|
18
23
|
local?: string
|
|
24
|
+
/** JSON key where MCP servers are stored */
|
|
19
25
|
mcpKey: string
|
|
20
26
|
}
|
|
21
27
|
|
|
@@ -27,7 +33,22 @@ try {
|
|
|
27
33
|
execSync("npm install @toon-format/toon", { cwd: projectRoot, stdio: "inherit" })
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Detect all supported AI coding agents on the system.
|
|
38
|
+
*
|
|
39
|
+
* Scans for configuration files in both global (~/.config/) and local
|
|
40
|
+
* (.opencode/, .vscode/, etc.) locations.
|
|
41
|
+
*
|
|
42
|
+
* @returns Array of detected agent configurations
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const agents = detectAgents()
|
|
47
|
+
* for (const agent of agents) {
|
|
48
|
+
* console.log(`${agent.name}: ${agent.local || "not found"}`)
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
31
52
|
function detectAgents(): Agent[] {
|
|
32
53
|
const agents: Agent[] = []
|
|
33
54
|
|
|
@@ -94,25 +115,40 @@ function detectAgents(): Agent[] {
|
|
|
94
115
|
return agents
|
|
95
116
|
}
|
|
96
117
|
|
|
97
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Install memory directory for OpenCode.
|
|
120
|
+
*
|
|
121
|
+
* Creates `.opencode/memory/` directory and initial `data.toon` if needed.
|
|
122
|
+
* The MCP server handles all memory operations - no plugin needed.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```bash
|
|
126
|
+
* npx toon-memory init # Calls installOpenCodeTools()
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
98
129
|
function installOpenCodeTools(): void {
|
|
99
|
-
const toolsDir = join(projectRoot, ".opencode", "tools")
|
|
100
130
|
const memoryDir = join(projectRoot, ".opencode", "memory")
|
|
101
131
|
const memoryFile = join(memoryDir, "data.toon")
|
|
102
132
|
|
|
103
|
-
if (!existsSync(toolsDir)) mkdirSync(toolsDir, { recursive: true })
|
|
104
133
|
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true })
|
|
105
134
|
|
|
106
|
-
cpSync(join(sourceDir, "memory.ts"), join(toolsDir, "memory.ts"))
|
|
107
|
-
console.log(" Copied memory.ts to .opencode/tools/")
|
|
108
|
-
|
|
109
135
|
if (!existsSync(memoryFile)) {
|
|
110
|
-
writeFileSync(memoryFile, "version: 1\
|
|
136
|
+
writeFileSync(memoryFile, "version: 1\n[0|]\n")
|
|
111
137
|
console.log(" Created .opencode/memory/data.toon")
|
|
112
138
|
}
|
|
113
139
|
}
|
|
114
140
|
|
|
115
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Add `.opencode/memory/` to `.gitignore` if not already present.
|
|
143
|
+
*
|
|
144
|
+
* Creates `.gitignore` if it doesn't exist, or appends the memory
|
|
145
|
+
* exclusion pattern to the existing file.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* ensureGitignore() // Adds .opencode/memory/ to .gitignore
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
116
152
|
function ensureGitignore(): void {
|
|
117
153
|
const gitignorePath = join(projectRoot, ".gitignore")
|
|
118
154
|
const entry = ".opencode/memory/"
|
|
@@ -130,7 +166,20 @@ function ensureGitignore(): void {
|
|
|
130
166
|
}
|
|
131
167
|
}
|
|
132
168
|
|
|
133
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Install MCP server configuration for an agent.
|
|
171
|
+
*
|
|
172
|
+
* Adds the `toon-memory` MCP server entry to the agent's config file.
|
|
173
|
+
*
|
|
174
|
+
* @param agent - Agent configuration with config path and MCP key
|
|
175
|
+
* @param scope - "global" or "local" installation scope
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const agent = { name: "opencode", local: ".opencode/opencode.json", mcpKey: "mcp" }
|
|
180
|
+
* installMCPConfig(agent, "local")
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
134
183
|
function installMCPConfig(agent: Agent, scope: string): void {
|
|
135
184
|
const configPath = scope === "global" ? agent.global : agent.local
|
|
136
185
|
|
|
@@ -154,16 +203,34 @@ function installMCPConfig(agent: Agent, scope: string): void {
|
|
|
154
203
|
const mcpKey = agent.mcpKey || "mcpServers"
|
|
155
204
|
if (!config[mcpKey]) config[mcpKey] = {}
|
|
156
205
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
206
|
+
// OpenCode uses a different format than other agents
|
|
207
|
+
if (agent.name === "opencode") {
|
|
208
|
+
config[mcpKey]["toon-memory"] = {
|
|
209
|
+
enabled: true,
|
|
210
|
+
type: "local",
|
|
211
|
+
command: ["npx", "-y", "toon-memory", "mcp"]
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
config[mcpKey]["toon-memory"] = {
|
|
215
|
+
command: "npx",
|
|
216
|
+
args: ["-y", "toon-memory", "mcp"]
|
|
217
|
+
}
|
|
160
218
|
}
|
|
161
219
|
|
|
162
220
|
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
163
221
|
console.log(` MCP server added to ${configPath}`)
|
|
164
222
|
}
|
|
165
223
|
|
|
166
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Uninstall toon-memory from all detected agents.
|
|
226
|
+
*
|
|
227
|
+
* Removes MCP server configurations and custom tools from all agents.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```bash
|
|
231
|
+
* npx toon-memory uninstall
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
167
234
|
function uninstall(): void {
|
|
168
235
|
console.log("\nš§ toon-memory uninstaller\n")
|
|
169
236
|
|
|
@@ -198,7 +265,20 @@ function uninstall(): void {
|
|
|
198
265
|
console.log("\nā
toon-memory uninstalled from all agents\n")
|
|
199
266
|
}
|
|
200
267
|
|
|
201
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Initialize toon-memory for all detected agents (non-interactive).
|
|
270
|
+
*
|
|
271
|
+
* Installs MCP server configs, creates memory directory, and
|
|
272
|
+
* updates `.gitignore`.
|
|
273
|
+
*
|
|
274
|
+
* @param scope - "local" (default) or "global" installation scope
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```bash
|
|
278
|
+
* npx toon-memory init # Local install
|
|
279
|
+
* npx toon-memory init global # Global install
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
202
282
|
function init(scope: string = "local"): void {
|
|
203
283
|
console.log("\nš§ toon-memory init\n")
|
|
204
284
|
|
|
@@ -220,7 +300,16 @@ function init(scope: string = "local"): void {
|
|
|
220
300
|
console.log("Done! Restart your agent to use memory tools.\n")
|
|
221
301
|
}
|
|
222
302
|
|
|
223
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Show toon-memory installation status.
|
|
305
|
+
*
|
|
306
|
+
* Displays version, memory entry count, and agent configuration status.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```bash
|
|
310
|
+
* npx toon-memory status
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
224
313
|
function status(): void {
|
|
225
314
|
console.log("\nš§ toon-memory status\n")
|
|
226
315
|
|
|
@@ -272,7 +361,16 @@ function status(): void {
|
|
|
272
361
|
console.log("")
|
|
273
362
|
}
|
|
274
363
|
|
|
275
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Upgrade toon-memory to the latest version.
|
|
366
|
+
*
|
|
367
|
+
* Checks npm registry for updates and installs if available.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```bash
|
|
371
|
+
* npx toon-memory upgrade
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
276
374
|
function upgrade(): void {
|
|
277
375
|
console.log("\nš§ toon-memory upgrade\n")
|
|
278
376
|
|
|
@@ -291,7 +389,16 @@ function upgrade(): void {
|
|
|
291
389
|
}
|
|
292
390
|
}
|
|
293
391
|
|
|
294
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Display memory statistics.
|
|
394
|
+
*
|
|
395
|
+
* Shows entry counts by category, last update date, and file size.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```bash
|
|
399
|
+
* npx toon-memory stats
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
295
402
|
function stats(): void {
|
|
296
403
|
console.log("\nš§ toon-memory stats\n")
|
|
297
404
|
|
|
@@ -333,7 +440,17 @@ function stats(): void {
|
|
|
333
440
|
console.log("")
|
|
334
441
|
}
|
|
335
442
|
|
|
336
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Export memory to JSON format.
|
|
445
|
+
*
|
|
446
|
+
* Creates a `toon-memory-export.json` file with all entries
|
|
447
|
+
* for backup or transfer to another project.
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```bash
|
|
451
|
+
* npx toon-memory export
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
337
454
|
function exportMemory(): void {
|
|
338
455
|
console.log("\nš§ toon-memory export\n")
|
|
339
456
|
|
|
@@ -374,7 +491,18 @@ function exportMemory(): void {
|
|
|
374
491
|
console.log(` ${outputPath}\n`)
|
|
375
492
|
}
|
|
376
493
|
|
|
377
|
-
|
|
494
|
+
/**
|
|
495
|
+
* Import memory from JSON file.
|
|
496
|
+
*
|
|
497
|
+
* Imports entries from a JSON export, skipping duplicates based on key.
|
|
498
|
+
*
|
|
499
|
+
* @param file - Path to JSON file (relative or absolute)
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```bash
|
|
503
|
+
* npx toon-memory import toon-memory-export.json
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
378
506
|
function importMemory(): void {
|
|
379
507
|
console.log("\nš§ toon-memory import\n")
|
|
380
508
|
|
|
@@ -446,7 +574,104 @@ function importMemory(): void {
|
|
|
446
574
|
console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates\n`)
|
|
447
575
|
}
|
|
448
576
|
|
|
449
|
-
|
|
577
|
+
/** Watch mode options */
|
|
578
|
+
interface WatchOptions {
|
|
579
|
+
/** Backup interval in minutes (default: 5) */
|
|
580
|
+
interval: number
|
|
581
|
+
/** Maximum number of backups to keep (0 = unlimited) */
|
|
582
|
+
maxBackups: number
|
|
583
|
+
/** Enable gzip compression for backups */
|
|
584
|
+
compress: boolean
|
|
585
|
+
/** Enable file logging */
|
|
586
|
+
logFile: boolean
|
|
587
|
+
/** Log file path */
|
|
588
|
+
logPath: string
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Parse watch CLI arguments into WatchOptions */
|
|
592
|
+
function parseWatchOptions(args: string[]): WatchOptions {
|
|
593
|
+
const opts: WatchOptions = {
|
|
594
|
+
interval: 5,
|
|
595
|
+
maxBackups: 10,
|
|
596
|
+
compress: false,
|
|
597
|
+
logFile: false,
|
|
598
|
+
logPath: ""
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
for (let i = 1; i < args.length; i++) {
|
|
602
|
+
const arg = args[i]
|
|
603
|
+
if (arg === "--compress" || arg === "-c") {
|
|
604
|
+
opts.compress = true
|
|
605
|
+
} else if (arg === "--log" || arg === "-l") {
|
|
606
|
+
opts.logFile = true
|
|
607
|
+
opts.logPath = args[++i] || join(projectRoot, ".opencode", "memory", "watch.log")
|
|
608
|
+
} else if (arg === "--max-backups" || arg === "-m") {
|
|
609
|
+
opts.maxBackups = parseInt(args[++i]) || 10
|
|
610
|
+
} else if (!arg.startsWith("-")) {
|
|
611
|
+
opts.interval = parseInt(arg) || 5
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return opts
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Write a line to the watch log file */
|
|
619
|
+
function writeWatchLog(logPath: string, message: string): void {
|
|
620
|
+
if (!logPath) return
|
|
621
|
+
const timestamp = new Date().toISOString()
|
|
622
|
+
const logLine = `[${timestamp}] ${message}\n`
|
|
623
|
+
writeFileSync(logPath, logLine, { flag: "a" })
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Get list of backup files sorted by creation time (oldest first) */
|
|
627
|
+
function getBackupFiles(backupDir: string): string[] {
|
|
628
|
+
if (!existsSync(backupDir)) return []
|
|
629
|
+
|
|
630
|
+
return readdirSync(backupDir)
|
|
631
|
+
.filter(f => f.startsWith("backup-") && (f.endsWith(".toon") || f.endsWith(".gz")))
|
|
632
|
+
.map(f => join(backupDir, f))
|
|
633
|
+
.sort((a, b) => statSync(a).mtimeMs - statSync(b).mtimeMs)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** Remove oldest backups if we exceed maxBackups */
|
|
637
|
+
function pruneBackups(backupDir: string, maxBackups: number): number {
|
|
638
|
+
if (maxBackups <= 0) return 0
|
|
639
|
+
|
|
640
|
+
const files = getBackupFiles(backupDir)
|
|
641
|
+
const excess = files.length - maxBackups
|
|
642
|
+
|
|
643
|
+
if (excess <= 0) return 0
|
|
644
|
+
|
|
645
|
+
for (let i = 0; i < excess; i++) {
|
|
646
|
+
unlinkSync(files[i])
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return excess
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Compress content with gzip */
|
|
653
|
+
function compressData(data: string): Buffer {
|
|
654
|
+
return gzipSync(Buffer.from(data, "utf-8"))
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** Decompress gzip data to string */
|
|
658
|
+
function decompressData(data: Buffer): string {
|
|
659
|
+
return gunzipSync(data).toString("utf-8")
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Watch mode - backup memory every N minutes
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```bash
|
|
667
|
+
* npx toon-memory watch # Default: 5 min interval, 10 max backups
|
|
668
|
+
* npx toon-memory watch 10 # 10 minute interval
|
|
669
|
+
* npx toon-memory watch -c # Enable compression
|
|
670
|
+
* npx toon-memory watch -l # Enable file logging
|
|
671
|
+
* npx toon-memory watch -m 20 # Keep max 20 backups
|
|
672
|
+
* npx toon-memory watch 15 -c -l -m 5 # All options
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
450
675
|
function watch(): void {
|
|
451
676
|
console.log("\nš§ toon-memory watch\n")
|
|
452
677
|
|
|
@@ -460,36 +685,83 @@ function watch(): void {
|
|
|
460
685
|
|
|
461
686
|
if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true })
|
|
462
687
|
|
|
463
|
-
const
|
|
464
|
-
|
|
688
|
+
const opts = parseWatchOptions(args)
|
|
689
|
+
|
|
690
|
+
if (opts.logFile) {
|
|
691
|
+
const logDir = dirname(opts.logPath)
|
|
692
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true })
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
console.log(`Watching memory file every ${opts.interval} minutes...`)
|
|
696
|
+
console.log(`Max backups: ${opts.maxBackups === 0 ? "unlimited" : opts.maxBackups}`)
|
|
697
|
+
console.log(`Compression: ${opts.compress ? "enabled" : "disabled"}`)
|
|
698
|
+
console.log(`Logging: ${opts.logFile ? `enabled (${opts.logPath})` : "disabled"}`)
|
|
465
699
|
console.log(`Press Ctrl+C to stop\n`)
|
|
466
700
|
|
|
467
701
|
let lastContent = readFileSync(memoryFile, "utf-8")
|
|
702
|
+
let lastHash = hashContent(lastContent)
|
|
468
703
|
let backupCount = 0
|
|
469
704
|
|
|
705
|
+
if (opts.logFile) writeWatchLog(opts.logPath, "Watch started")
|
|
706
|
+
|
|
470
707
|
const backup = () => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
708
|
+
try {
|
|
709
|
+
const currentContent = readFileSync(memoryFile, "utf-8")
|
|
710
|
+
const currentHash = hashContent(currentContent)
|
|
711
|
+
|
|
712
|
+
if (currentHash !== lastHash) {
|
|
713
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
714
|
+
const ext = opts.compress ? ".toon.gz" : ".toon"
|
|
715
|
+
const backupFile = join(backupDir, `backup-${timestamp}${ext}`)
|
|
716
|
+
|
|
717
|
+
if (opts.compress) {
|
|
718
|
+
writeFileSync(backupFile, compressData(currentContent))
|
|
719
|
+
} else {
|
|
720
|
+
writeFileSync(backupFile, currentContent)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
backupCount++
|
|
724
|
+
console.log(`š¦ Backup #${backupCount} created: ${timestamp}`)
|
|
725
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Backup #${backupCount}: ${timestamp}`)
|
|
726
|
+
|
|
727
|
+
lastContent = currentContent
|
|
728
|
+
lastHash = currentHash
|
|
729
|
+
|
|
730
|
+
const pruned = pruneBackups(backupDir, opts.maxBackups)
|
|
731
|
+
if (pruned > 0) {
|
|
732
|
+
console.log(`šļø Pruned ${pruned} old backup(s)`)
|
|
733
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Pruned ${pruned} old backup(s)`)
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
const msg = `Error creating backup: ${(err as Error).message}`
|
|
738
|
+
console.error(`ā ${msg}`)
|
|
739
|
+
if (opts.logFile) writeWatchLog(opts.logPath, msg)
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** Simple hash for change detection (not cryptographic) */
|
|
744
|
+
function hashContent(content: string): string {
|
|
745
|
+
let hash = 0
|
|
746
|
+
for (let i = 0; i < content.length; i++) {
|
|
747
|
+
const char = content.charCodeAt(i)
|
|
748
|
+
hash = ((hash << 5) - hash) + char
|
|
749
|
+
hash = hash & hash
|
|
480
750
|
}
|
|
751
|
+
return hash.toString(36)
|
|
481
752
|
}
|
|
482
753
|
|
|
483
754
|
// Initial backup
|
|
484
755
|
backup()
|
|
485
756
|
|
|
486
757
|
// Set interval
|
|
487
|
-
const interval = setInterval(backup,
|
|
758
|
+
const interval = setInterval(backup, opts.interval * 60 * 1000)
|
|
488
759
|
|
|
489
760
|
// Handle Ctrl+C
|
|
490
761
|
process.on("SIGINT", () => {
|
|
491
762
|
clearInterval(interval)
|
|
492
763
|
console.log(`\nā
Watch stopped. ${backupCount} backups created.\n`)
|
|
764
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Watch stopped. ${backupCount} backups created.`)
|
|
493
765
|
process.exit(0)
|
|
494
766
|
})
|
|
495
767
|
|