@kevin0181/memoc 1.0.5 → 1.0.8
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 +7 -3
- package/bin/cli.js +138 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,9 +46,13 @@ npx @kevin0181/memoc update
|
|
|
46
46
|
# Print current status in ~10 lines
|
|
47
47
|
npx @kevin0181/memoc summary
|
|
48
48
|
|
|
49
|
-
#
|
|
50
|
-
npx @kevin0181/memoc search "auth"
|
|
51
|
-
npx @kevin0181/memoc search "auth" --snippets --limit 5
|
|
49
|
+
# Search memory/agent docs first (token-efficient)
|
|
50
|
+
npx @kevin0181/memoc search "auth"
|
|
51
|
+
npx @kevin0181/memoc search "auth" --snippets --limit 5
|
|
52
|
+
|
|
53
|
+
# Search project source/text files only when memory is not enough
|
|
54
|
+
npx @kevin0181/memoc grep "GetParticles"
|
|
55
|
+
npx @kevin0181/memoc grep "GetParticles" --snippets --limit 5
|
|
52
56
|
|
|
53
57
|
# Estimate token cost of current memory files
|
|
54
58
|
npx @kevin0181/memoc tokens
|
package/bin/cli.js
CHANGED
|
@@ -208,16 +208,16 @@ function write(filePath, content) {
|
|
|
208
208
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
function tplMemocCmdWrapper() {
|
|
212
|
-
return `@echo off\r\
|
|
211
|
+
function tplMemocCmdWrapper(cliPath = runtimeCliPath()) {
|
|
212
|
+
return `@echo off\r\nnode "${escapeCmdPath(cliPath)}" %*\r\n`;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
function tplMemocPs1Wrapper() {
|
|
216
|
-
return
|
|
215
|
+
function tplMemocPs1Wrapper(cliPath = runtimeCliPath()) {
|
|
216
|
+
return `& node ${psSingleQuote(cliPath)} @args\nexit $LASTEXITCODE\n`;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function tplMemocShWrapper() {
|
|
220
|
-
return `#!/bin/sh\nexec
|
|
219
|
+
function tplMemocShWrapper(cliPath = runtimeCliPath()) {
|
|
220
|
+
return `#!/bin/sh\nexec node ${shellSingleQuote(cliPath)} "$@"\n`;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
function defaultUserBinDir() {
|
|
@@ -228,6 +228,18 @@ function defaultUserBinDir() {
|
|
|
228
228
|
return path.join(process.env.HOME || process.cwd(), '.local', 'bin');
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function defaultRuntimeDir() {
|
|
232
|
+
if (process.env.MEMOC_RUNTIME_DIR) return process.env.MEMOC_RUNTIME_DIR;
|
|
233
|
+
if (currentPlatform() === 'win32') {
|
|
234
|
+
return path.join(process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || process.cwd(), 'AppData', 'Local'), 'memoc', 'runtime');
|
|
235
|
+
}
|
|
236
|
+
return path.join(process.env.HOME || process.cwd(), '.local', 'share', 'memoc', 'runtime');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function runtimeCliPath() {
|
|
240
|
+
return path.join(defaultRuntimeDir(), 'bin', 'cli.js');
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
function tplEnvPs1() {
|
|
232
244
|
return `$memocBin = Join-Path $PSScriptRoot 'bin'\n$parts = $env:PATH -split [IO.Path]::PathSeparator\nif ($parts -notcontains $memocBin) {\n $env:PATH = \"$memocBin$([IO.Path]::PathSeparator)$env:PATH\"\n}\n`;
|
|
233
245
|
}
|
|
@@ -237,10 +249,11 @@ function tplEnvSh() {
|
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
function ensurePathHelpers(dir, mark) {
|
|
252
|
+
const cliPath = ensureRuntimeInstall(mark);
|
|
240
253
|
const files = [
|
|
241
|
-
[path.join(dir, '.memoc', 'bin', 'memoc.cmd'), tplMemocCmdWrapper, false],
|
|
242
|
-
[path.join(dir, '.memoc', 'bin', 'memoc.ps1'), tplMemocPs1Wrapper, false],
|
|
243
|
-
[path.join(dir, '.memoc', 'bin', 'memoc'), tplMemocShWrapper, true],
|
|
254
|
+
[path.join(dir, '.memoc', 'bin', 'memoc.cmd'), () => tplMemocCmdWrapper(cliPath), false],
|
|
255
|
+
[path.join(dir, '.memoc', 'bin', 'memoc.ps1'), () => tplMemocPs1Wrapper(cliPath), false],
|
|
256
|
+
[path.join(dir, '.memoc', 'bin', 'memoc'), () => tplMemocShWrapper(cliPath), true],
|
|
244
257
|
[path.join(dir, '.memoc', 'env.ps1'), tplEnvPs1, false],
|
|
245
258
|
[path.join(dir, '.memoc', 'env.sh'), tplEnvSh, true],
|
|
246
259
|
];
|
|
@@ -255,15 +268,15 @@ function ensurePathHelpers(dir, mark) {
|
|
|
255
268
|
|
|
256
269
|
function ensureUserLauncher(mark) {
|
|
257
270
|
const userBin = defaultUserBinDir();
|
|
258
|
-
writeLaunchers(userBin, mark, 'user bin');
|
|
271
|
+
writeLaunchers(userBin, mark, 'user bin', ensureRuntimeInstall(mark));
|
|
259
272
|
return userBin;
|
|
260
273
|
}
|
|
261
274
|
|
|
262
|
-
function writeLaunchers(binDir, mark, label) {
|
|
275
|
+
function writeLaunchers(binDir, mark, label, cliPath = ensureRuntimeInstall(mark)) {
|
|
263
276
|
const files = [
|
|
264
|
-
[path.join(binDir, 'memoc.cmd'), tplMemocCmdWrapper, false],
|
|
265
|
-
[path.join(binDir, 'memoc.ps1'), tplMemocPs1Wrapper, false],
|
|
266
|
-
[path.join(binDir, 'memoc'), tplMemocShWrapper, true],
|
|
277
|
+
[path.join(binDir, 'memoc.cmd'), () => tplMemocCmdWrapper(cliPath), false],
|
|
278
|
+
[path.join(binDir, 'memoc.ps1'), () => tplMemocPs1Wrapper(cliPath), false],
|
|
279
|
+
[path.join(binDir, 'memoc'), () => tplMemocShWrapper(cliPath), true],
|
|
267
280
|
];
|
|
268
281
|
|
|
269
282
|
for (const [fp, tpl, executable] of files) {
|
|
@@ -341,10 +354,32 @@ function ensureCurrentPathLauncher(mark) {
|
|
|
341
354
|
mark('skip', 'active PATH launcher (no writable PATH directory found)');
|
|
342
355
|
return false;
|
|
343
356
|
}
|
|
344
|
-
writeLaunchers(target, mark, 'active PATH');
|
|
357
|
+
writeLaunchers(target, mark, 'active PATH', ensureRuntimeInstall(mark));
|
|
345
358
|
return true;
|
|
346
359
|
}
|
|
347
360
|
|
|
361
|
+
function ensureRuntimeInstall(mark) {
|
|
362
|
+
const runtimeDir = defaultRuntimeDir();
|
|
363
|
+
const sourceRoot = path.join(__dirname, '..');
|
|
364
|
+
const files = [
|
|
365
|
+
[path.join(sourceRoot, 'bin', 'cli.js'), path.join(runtimeDir, 'bin', 'cli.js')],
|
|
366
|
+
[path.join(sourceRoot, 'package.json'), path.join(runtimeDir, 'package.json')],
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
for (const [src, dest] of files) {
|
|
370
|
+
try {
|
|
371
|
+
const content = fs.readFileSync(src, 'utf8');
|
|
372
|
+
const changed = writeIfChanged(dest, content);
|
|
373
|
+
mark(changed, `runtime ${path.relative(runtimeDir, dest)}`);
|
|
374
|
+
} catch {
|
|
375
|
+
mark('skip', `runtime ${path.basename(dest)} unavailable`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
chmodExecutable(path.join(runtimeDir, 'bin', 'cli.js'));
|
|
380
|
+
return path.join(runtimeDir, 'bin', 'cli.js');
|
|
381
|
+
}
|
|
382
|
+
|
|
348
383
|
function findWritablePathDir() {
|
|
349
384
|
const dirs = [...new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean))];
|
|
350
385
|
const npmBin = npmGlobalBinDir();
|
|
@@ -447,6 +482,14 @@ function shellSingleQuote(value) {
|
|
|
447
482
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
448
483
|
}
|
|
449
484
|
|
|
485
|
+
function psSingleQuote(value) {
|
|
486
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function escapeCmdPath(value) {
|
|
490
|
+
return String(value).replace(/"/g, '""');
|
|
491
|
+
}
|
|
492
|
+
|
|
450
493
|
function samePath(a, b) {
|
|
451
494
|
if (!a || !b) return false;
|
|
452
495
|
const norm = p => path.resolve(p).toLowerCase().replace(/[\\/]+$/, '');
|
|
@@ -503,7 +546,8 @@ function managedBlock() {
|
|
|
503
546
|
## Before Opening More Files
|
|
504
547
|
- [ ] Run memoc commands in this order: \`memoc search "<query>"\` → \`.\\.memoc\\bin\\memoc.cmd search "<query>"\` (Windows) or \`.memoc/bin/memoc search "<query>"\` (sh) → \`npx @kevin0181/memoc search "<query>"\`
|
|
505
548
|
- [ ] Open on demand: \`02\` status · \`04\` resume · \`06\` rules · \`llms.txt\` map
|
|
506
|
-
- [ ]
|
|
549
|
+
- [ ] If memory search is not enough, search project files with \`memoc grep "<query>" --limit 5\`
|
|
550
|
+
- [ ] Keep output small: \`summary\`, \`search --limit\`, \`grep --limit\`, \`--snippets\`
|
|
507
551
|
|
|
508
552
|
## Before Finishing _(update only applicable files; skip Q&A / throwaway exploration)_
|
|
509
553
|
- [ ] Code/config/deps changed → \`02\` (version, commands list, Last synced) + \`session-summary.md\` (status, changed, open tasks)
|
|
@@ -816,7 +860,8 @@ On-demand reference only. The entry-file managed block is authoritative.
|
|
|
816
860
|
|
|
817
861
|
## Search First
|
|
818
862
|
|
|
819
|
-
\`memoc search "<query>"\` — returns file:line matches across
|
|
863
|
+
\`memoc search "<query>"\` — returns file:line matches across memory and agent docs only.
|
|
864
|
+
\`memoc grep "<query>"\` — searches project source/text files when memory docs are not enough.
|
|
820
865
|
If \`memoc\` is not on PATH, try \`.\\.memoc\\bin\\memoc.cmd search "<query>"\` on Windows or \`.memoc/bin/memoc search "<query>"\` in sh, then \`npx @kevin0181/memoc search "<query>"\`.
|
|
821
866
|
Use it before opening any file to avoid reading more than needed.
|
|
822
867
|
`;
|
|
@@ -996,9 +1041,13 @@ memoc update
|
|
|
996
1041
|
# Tiny status overview
|
|
997
1042
|
memoc summary
|
|
998
1043
|
|
|
999
|
-
#
|
|
1000
|
-
memoc search "<query>" --limit 12
|
|
1044
|
+
# Search memory first; add --snippets only when needed
|
|
1045
|
+
memoc search "<query>" --limit 12
|
|
1001
1046
|
memoc search "<query>" --snippets --limit 5
|
|
1047
|
+
|
|
1048
|
+
# Search project source/text files when memory is not enough
|
|
1049
|
+
memoc grep "<query>" --limit 12
|
|
1050
|
+
memoc grep "<query>" --snippets --limit 5
|
|
1002
1051
|
\`\`\`
|
|
1003
1052
|
|
|
1004
1053
|
If \`memoc\` is not on PATH, use \`.\\.memoc\\bin\\memoc.cmd <command>\` on Windows or \`.memoc/bin/memoc <command>\` in sh. If that is unavailable, use \`npx @kevin0181/memoc <command>\`.
|
|
@@ -1007,9 +1056,9 @@ If \`memoc\` is not on PATH, use \`.\\.memoc\\bin\\memoc.cmd <command>\` on Wind
|
|
|
1007
1056
|
|
|
1008
1057
|
1. Entry-file managed block.
|
|
1009
1058
|
2. \`.memoc/session-summary.md\` only.
|
|
1010
|
-
3. Search
|
|
1011
|
-
4.
|
|
1012
|
-
5.
|
|
1059
|
+
3. Search memory first: \`memoc search "<query>"\`.
|
|
1060
|
+
4. If memory is not enough, search project files: \`memoc grep "<query>" --limit 5\`.
|
|
1061
|
+
5. Use \`--snippets\` only when file names are not enough.
|
|
1013
1062
|
|
|
1014
1063
|
## When To Run Memory Updates
|
|
1015
1064
|
|
|
@@ -1103,7 +1152,7 @@ Use this local skill after meaningful project work so future agents can continue
|
|
|
1103
1152
|
## Required Reads
|
|
1104
1153
|
|
|
1105
1154
|
1. \`.memoc/session-summary.md\`
|
|
1106
|
-
2. \`memoc summary\` or \`memoc search "<query>"\`;
|
|
1155
|
+
2. \`memoc summary\` or \`memoc search "<query>"\`; use \`memoc grep "<query>"\` only when source/text search is needed
|
|
1107
1156
|
3. Open only files you will use or update.
|
|
1108
1157
|
|
|
1109
1158
|
## Maintenance Checklist
|
|
@@ -1427,9 +1476,9 @@ function runAdd(dir) {
|
|
|
1427
1476
|
// SEARCH
|
|
1428
1477
|
// ═══════════════════════════════════════════════════════════════════
|
|
1429
1478
|
|
|
1430
|
-
function runSearch(dir) {
|
|
1431
|
-
const rawArgs = process.argv.slice(3);
|
|
1432
|
-
const opts = { mode: 'files', limit: 12, all: false };
|
|
1479
|
+
function runSearch(dir, scope = 'memory') {
|
|
1480
|
+
const rawArgs = process.argv.slice(3);
|
|
1481
|
+
const opts = { mode: 'files', limit: 12, all: false };
|
|
1433
1482
|
const queryParts = [];
|
|
1434
1483
|
|
|
1435
1484
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
@@ -1450,19 +1499,12 @@ function runSearch(dir) {
|
|
|
1450
1499
|
queryParts.push(arg);
|
|
1451
1500
|
}
|
|
1452
1501
|
|
|
1453
|
-
const query = queryParts.join(' ').toLowerCase();
|
|
1454
|
-
|
|
1455
|
-
const searchRoots = [
|
|
1456
|
-
path.join(dir, '.memoc'),
|
|
1457
|
-
path.join(dir, 'skills'),
|
|
1458
|
-
path.join(dir, 'llms.txt'),
|
|
1459
|
-
path.join(dir, 'AGENTS.md'),
|
|
1460
|
-
path.join(dir, 'CLAUDE.md'),
|
|
1461
|
-
...Object.values(AGENT_REGISTRY).map(agent => path.join(dir, agent.file)),
|
|
1462
|
-
];
|
|
1502
|
+
const query = queryParts.join(' ').toLowerCase();
|
|
1503
|
+
|
|
1504
|
+
const searchRoots = scope === 'project' ? [dir] : memorySearchRoots(dir);
|
|
1463
1505
|
|
|
1464
1506
|
if (!query) {
|
|
1465
|
-
// No query — list
|
|
1507
|
+
// No query — list searchable files sorted by recency
|
|
1466
1508
|
const allFiles = [];
|
|
1467
1509
|
function collectFile(fp) {
|
|
1468
1510
|
if (!fs.existsSync(fp)) return;
|
|
@@ -1476,8 +1518,10 @@ function runSearch(dir) {
|
|
|
1476
1518
|
for (const entry of fs.readdirSync(d)) {
|
|
1477
1519
|
const fp = path.join(d, entry);
|
|
1478
1520
|
try {
|
|
1479
|
-
|
|
1480
|
-
|
|
1521
|
+
const st = fs.statSync(fp);
|
|
1522
|
+
if (st.isDirectory()) {
|
|
1523
|
+
if (!shouldSkipSearchDir(entry)) collectDir(fp);
|
|
1524
|
+
} else if (isSearchableFile(fp, entry, st, scope)) collectFile(fp);
|
|
1481
1525
|
} catch {}
|
|
1482
1526
|
}
|
|
1483
1527
|
}
|
|
@@ -1498,11 +1542,15 @@ function runSearch(dir) {
|
|
|
1498
1542
|
|
|
1499
1543
|
const matchesByFile = new Map(); // rel -> { matches: [], mtime: number }
|
|
1500
1544
|
|
|
1501
|
-
function searchFile(fp) {
|
|
1502
|
-
if (!fs.existsSync(fp)) return;
|
|
1503
|
-
const rel = path.relative(dir, fp);
|
|
1504
|
-
let mtime = 0;
|
|
1505
|
-
try {
|
|
1545
|
+
function searchFile(fp) {
|
|
1546
|
+
if (!fs.existsSync(fp)) return;
|
|
1547
|
+
const rel = path.relative(dir, fp);
|
|
1548
|
+
let mtime = 0;
|
|
1549
|
+
try {
|
|
1550
|
+
const st = fs.statSync(fp);
|
|
1551
|
+
if (!isSearchableFile(fp, path.basename(fp), st, scope)) return;
|
|
1552
|
+
mtime = st.mtimeMs;
|
|
1553
|
+
} catch {}
|
|
1506
1554
|
const lines = fs.readFileSync(fp, 'utf8').split('\n');
|
|
1507
1555
|
lines.forEach((line, i) => {
|
|
1508
1556
|
if (line.toLowerCase().includes(query)) {
|
|
@@ -1517,8 +1565,10 @@ function runSearch(dir) {
|
|
|
1517
1565
|
for (const entry of fs.readdirSync(d)) {
|
|
1518
1566
|
const fp = path.join(d, entry);
|
|
1519
1567
|
try {
|
|
1520
|
-
|
|
1521
|
-
|
|
1568
|
+
const st = fs.statSync(fp);
|
|
1569
|
+
if (st.isDirectory()) {
|
|
1570
|
+
if (!shouldSkipSearchDir(entry)) walkDir(fp);
|
|
1571
|
+
} else if (isSearchableFile(fp, entry, st, scope)) searchFile(fp);
|
|
1522
1572
|
} catch {}
|
|
1523
1573
|
}
|
|
1524
1574
|
}
|
|
@@ -1552,7 +1602,44 @@ function runSearch(dir) {
|
|
|
1552
1602
|
console.log(`... ${snippets.length - limited.length} more matches. Use --all to show all, or --limit N.`);
|
|
1553
1603
|
}
|
|
1554
1604
|
}
|
|
1555
|
-
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function memorySearchRoots(dir) {
|
|
1608
|
+
return [
|
|
1609
|
+
path.join(dir, '.memoc'),
|
|
1610
|
+
path.join(dir, 'skills'),
|
|
1611
|
+
path.join(dir, 'llms.txt'),
|
|
1612
|
+
path.join(dir, 'AGENTS.md'),
|
|
1613
|
+
path.join(dir, 'CLAUDE.md'),
|
|
1614
|
+
...Object.values(AGENT_REGISTRY).map(agent => path.join(dir, agent.file)),
|
|
1615
|
+
];
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function shouldSkipSearchDir(name) {
|
|
1619
|
+
return new Set([
|
|
1620
|
+
'.git', 'node_modules', '.next', 'dist', 'build', 'out', 'coverage',
|
|
1621
|
+
'Saved', 'Intermediate', 'DerivedDataCache', 'Binaries',
|
|
1622
|
+
'.venv', 'venv', '__pycache__', '.pytest_cache',
|
|
1623
|
+
]).has(name);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function isSearchableFile(fp, name, st, scope = 'memory') {
|
|
1627
|
+
if (!st || !st.isFile()) return false;
|
|
1628
|
+
if (st.size > 1024 * 1024) return false;
|
|
1629
|
+
if (name === 'llms.txt' || name.endsWith('rules')) return true;
|
|
1630
|
+
const ext = path.extname(fp).toLowerCase();
|
|
1631
|
+
if (scope === 'memory') {
|
|
1632
|
+
return new Set(['.md', '.txt']).has(ext);
|
|
1633
|
+
}
|
|
1634
|
+
return new Set([
|
|
1635
|
+
'.md', '.txt', '.json', '.jsonc', '.yaml', '.yml', '.toml', '.ini', '.env',
|
|
1636
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
1637
|
+
'.py', '.rs', '.go', '.java', '.cs', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.hxx',
|
|
1638
|
+
'.html', '.css', '.scss', '.sass', '.vue', '.svelte',
|
|
1639
|
+
'.sql', '.graphql', '.gql', '.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
|
|
1640
|
+
'.xml', '.gradle', '.kts', '.cmake',
|
|
1641
|
+
]).has(ext);
|
|
1642
|
+
}
|
|
1556
1643
|
|
|
1557
1644
|
// ═══════════════════════════════════════════════════════════════════
|
|
1558
1645
|
// TOKENS — estimate token cost of current memory state
|
|
@@ -1730,7 +1817,8 @@ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
|
1730
1817
|
console.log(' tokens Estimate token cost of current memory files');
|
|
1731
1818
|
console.log(' compress Archive old log.md entries to keep file small');
|
|
1732
1819
|
console.log(' add <agent> Add entry file for a specific agent (run without args to list)');
|
|
1733
|
-
console.log(' search "<query>"
|
|
1820
|
+
console.log(' search "<query>" Search memory/agent docs (use --snippets for line matches)');
|
|
1821
|
+
console.log(' grep "<query>" Search project source/text files (use --snippets for line matches)');
|
|
1734
1822
|
console.log('\nSearch flags:');
|
|
1735
1823
|
console.log(' --files Show file names and match counts, sorted by relevance + recency (default)');
|
|
1736
1824
|
console.log(' --snippets Show matching lines');
|
|
@@ -1747,7 +1835,8 @@ if (cmd === 'summary') { runSummary(cwd); process.exit(0); }
|
|
|
1747
1835
|
if (cmd === 'tokens') { runTokens(cwd); process.exit(0); }
|
|
1748
1836
|
if (cmd === 'compress') { runCompress(cwd); process.exit(0); }
|
|
1749
1837
|
if (cmd === 'add') { runAdd(cwd); process.exit(0); }
|
|
1750
|
-
if (cmd === 'search') { runSearch(cwd);
|
|
1838
|
+
if (cmd === 'search') { runSearch(cwd, 'memory'); process.exit(0); }
|
|
1839
|
+
if (cmd === 'grep') { runSearch(cwd, 'project'); process.exit(0); }
|
|
1751
1840
|
|
|
1752
1841
|
console.error(`Unknown command: ${cmd}`);
|
|
1753
1842
|
console.error('Run "memoc --help" for usage.');
|