cc-md-search-cli 1.0.3 → 1.0.4
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/CHANGELOG.md +39 -0
- package/CONFIGURATION.md +113 -12
- package/README.md +28 -11
- package/bun.lock +94 -2
- package/package.json +8 -2
- package/skills/ccmds-config.md +5 -3
- package/src/cache/cache.js +166 -0
- package/src/cache/index.js +14 -0
- package/src/cli.js +222 -1142
- package/src/config/constants.js +63 -0
- package/src/config/directories.js +91 -0
- package/src/config/generator.js +29 -0
- package/src/config/index.js +9 -0
- package/src/config/loader.js +143 -0
- package/src/config/path-format.js +26 -0
- package/src/files/discovery.js +111 -0
- package/src/files/glob.js +116 -0
- package/src/files/index.js +6 -0
- package/src/index-persistence/flexsearch-index.js +571 -0
- package/src/index-persistence/index.js +20 -0
- package/src/index.js +89 -0
- package/src/init/config-builder.js +135 -0
- package/src/init/index.js +105 -0
- package/src/init/prompts.js +337 -0
- package/src/init/ui.js +168 -0
- package/src/init/validators.js +226 -0
- package/src/output/formatter.js +116 -0
- package/src/output/index.js +5 -0
- package/src/parsing/context.js +126 -0
- package/src/parsing/headings.js +72 -0
- package/src/parsing/index.js +8 -0
- package/src/parsing/markdown.js +41 -0
- package/src/parsing/sections.js +42 -0
- package/src/search/fuzzy.js +489 -0
- package/src/search/grep.js +80 -0
- package/src/search/index.js +11 -0
- package/src/version/index.js +5 -0
- package/src/version/update.js +44 -0
- package/tests/cli-commands.test.js +1 -1
- package/tests/cli-config-commands.test.js +30 -6
- package/tests/cross-runtime.test.js +7 -108
- package/tests/index-persistence.test.js +501 -0
- package/tests/init-config-builder.test.js +176 -0
- package/tests/init-validators.test.js +228 -0
- package/tests/integration.test.js +4 -4
- package/tests/search-fuzzy.test.js +53 -43
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.4] - 2026-01-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Interactive `init` wizard** - `ccmds init` now runs an interactive configuration wizard in TTY environments
|
|
10
|
+
- Prompts for document directories with option to create non-existent paths
|
|
11
|
+
- Named directory support with optional descriptions
|
|
12
|
+
- Common exclude pattern presets via checkbox selection
|
|
13
|
+
- Output mode selection
|
|
14
|
+
- Advanced options: result limit, fuzzy threshold, file extensions, caching
|
|
15
|
+
- **`--no-interactive` flag** - Skip the wizard and use non-interactive mode
|
|
16
|
+
- **`update` command** - `ccmds update` to check for and install latest version
|
|
17
|
+
- **Version check on `--version`** - Shows update availability when checking version
|
|
18
|
+
- **New dependencies** - @inquirer/prompts, chalk, ora for terminal UI
|
|
19
|
+
- **Context-aware previews for `find`** - Previews now show the paragraph or code block where the search term appears, instead of the beginning of the file
|
|
20
|
+
- Uses smart boundary detection (blank lines, headings, code fences)
|
|
21
|
+
- Preserves complete code blocks when match is in code
|
|
22
|
+
- Shows full bullet lists and paragraphs for text matches
|
|
23
|
+
- Falls back to description/file start if no body match found
|
|
24
|
+
- Configurable via `preview.maxLines` (default: 20) to limit context size
|
|
25
|
+
- **Extended query operators** - `'term` for exact substring match, `!term` for exclusion, space-separated terms for AND search
|
|
26
|
+
- **`--clear-cache` flag** - Clear search index cache before running `find` or `grep` commands
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Minimal config generation** - Generated configs only include values that differ from defaults
|
|
31
|
+
- **Exclude patterns prompt** - Now asks "Add exclude patterns?" first (default: No) before showing the list
|
|
32
|
+
- **README quickstart** - Simplified to use `ccmds init` interactive wizard
|
|
33
|
+
- **Replaced Fuse.js with FlexSearch** - New search engine with forward tokenization and resolution-based ranking
|
|
34
|
+
- **Index storage format** - Changed from `.ccmds-fuse-index.json` + `.ccmds-fuse-index-meta.json` files to `.ccmds-flexsearch/` directory structure
|
|
35
|
+
- **Search index version** - Bumped to version 5 for automatic cache invalidation
|
|
36
|
+
- **Default index path** - Changed from `.ccmds-fuse-index.json` to `.ccmds-flexsearch/`
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
- **Fuse.js dependency** - Replaced by FlexSearch
|
|
41
|
+
- **Fuse.js-specific config options** - `ignoreLocation`, `ignoreFieldNorm`, `distance` no longer apply
|
|
42
|
+
|
|
5
43
|
## [1.0.3] - 2026-01-22
|
|
6
44
|
|
|
7
45
|
### Added
|
|
@@ -99,6 +137,7 @@ All notable changes to this project will be documented in this file.
|
|
|
99
137
|
- Configurable search options (depth, limit, threshold)
|
|
100
138
|
- Claude Code skill integration (`ccmds`)
|
|
101
139
|
|
|
140
|
+
[1.0.4]: https://github.com/bjeber/cc-md-search-cli/compare/v1.0.3...v1.0.4
|
|
102
141
|
[1.0.3]: https://github.com/bjeber/cc-md-search-cli/compare/v1.0.2...v1.0.3
|
|
103
142
|
[1.0.2]: https://github.com/bjeber/cc-md-search-cli/compare/v1.0.1...v1.0.2
|
|
104
143
|
[1.0.1]: https://github.com/bjeber/cc-md-search-cli/compare/v1.0.0...v1.0.1
|
package/CONFIGURATION.md
CHANGED
|
@@ -23,17 +23,32 @@ ccmds find "authentication"
|
|
|
23
23
|
|
|
24
24
|
### Creating a Config File
|
|
25
25
|
|
|
26
|
+
The `init` command creates a `.ccmdsrc` configuration file. By default, it runs an **interactive wizard** when in a terminal (TTY).
|
|
27
|
+
|
|
26
28
|
```bash
|
|
27
|
-
#
|
|
28
|
-
ccmds init
|
|
29
|
+
# Interactive mode (default in terminal)
|
|
30
|
+
ccmds init # Guides you through configuration options
|
|
29
31
|
|
|
30
|
-
#
|
|
31
|
-
ccmds init -
|
|
32
|
+
# Non-interactive mode
|
|
33
|
+
ccmds init --no-interactive # Creates config with defaults
|
|
34
|
+
ccmds init -d ./docs ./wiki # Pre-fills directories, still interactive
|
|
35
|
+
ccmds init --no-interactive -d ./docs # Fully non-interactive
|
|
32
36
|
|
|
33
37
|
# Overwrite existing config
|
|
34
|
-
ccmds init --force
|
|
38
|
+
ccmds init --force # Works with both interactive/non-interactive
|
|
35
39
|
```
|
|
36
40
|
|
|
41
|
+
**Interactive mode** prompts for:
|
|
42
|
+
1. Document directories (with option to create non-existent dirs)
|
|
43
|
+
2. Exclude patterns (common presets + custom)
|
|
44
|
+
3. Output mode
|
|
45
|
+
4. Advanced options (limit, fuzzy threshold, extensions, caching)
|
|
46
|
+
|
|
47
|
+
**Non-interactive mode** is used automatically when:
|
|
48
|
+
- Running in CI/CD pipelines (no TTY)
|
|
49
|
+
- Using `--no-interactive` flag
|
|
50
|
+
- Piping input/output
|
|
51
|
+
|
|
37
52
|
### File Locations
|
|
38
53
|
|
|
39
54
|
The CLI looks for config files in this order:
|
|
@@ -73,6 +88,7 @@ The CLI looks for config files in this order:
|
|
|
73
88
|
}
|
|
74
89
|
},
|
|
75
90
|
"preview": {
|
|
91
|
+
"maxLines": 20,
|
|
76
92
|
"topResults": 600,
|
|
77
93
|
"midResults": 300,
|
|
78
94
|
"otherResults": 150
|
|
@@ -93,15 +109,19 @@ The CLI looks for config files in this order:
|
|
|
93
109
|
| `limit` | `number` | `10` | Default result limit for find |
|
|
94
110
|
| `fuzzy.threshold` | `number` | `0.4` | Fuzzy match threshold (0=exact, 1=loose) |
|
|
95
111
|
| `fuzzy.weights` | `object` | See above | Field weights for search scoring |
|
|
96
|
-
| `preview.
|
|
97
|
-
| `preview.
|
|
98
|
-
| `preview.
|
|
112
|
+
| `preview.maxLines` | `number` | `20` | Max lines for context-aware previews |
|
|
113
|
+
| `preview.topResults` | `number` | `600` | Fallback preview chars for results 1-3 |
|
|
114
|
+
| `preview.midResults` | `number` | `300` | Fallback preview chars for results 4-7 |
|
|
115
|
+
| `preview.otherResults` | `number` | `150` | Fallback preview chars for remaining |
|
|
99
116
|
| `frontmatterFields` | `string[]` | See above | Frontmatter fields to include |
|
|
100
117
|
| `extensions` | `string[]` | `[".md", ".markdown"]` | File extensions to search |
|
|
101
118
|
| `aliases` | `object` | `{}` | Command shortcuts (planned) |
|
|
102
119
|
| `cache.enabled` | `boolean` | `false` | Enable result caching |
|
|
103
120
|
| `cache.ttl` | `number` | `300` | Cache expiration in seconds |
|
|
104
121
|
| `cache.maxEntries` | `number` | `50` | Maximum cached queries |
|
|
122
|
+
| `index.enabled` | `boolean` | `true` | Enable FlexSearch index caching |
|
|
123
|
+
| `index.path` | `string` | `".ccmds-flexsearch/"` | Index directory location |
|
|
124
|
+
| `index.autoRebuild` | `boolean` | `true` | Auto-rebuild when files change |
|
|
105
125
|
|
|
106
126
|
---
|
|
107
127
|
|
|
@@ -382,6 +402,85 @@ Cache is stored in `.ccmds-cache.json` in the project root.
|
|
|
382
402
|
|
|
383
403
|
---
|
|
384
404
|
|
|
405
|
+
## Search Index (Performance)
|
|
406
|
+
|
|
407
|
+
The CLI automatically caches the FlexSearch index to dramatically improve search performance (60-80% faster on subsequent searches).
|
|
408
|
+
|
|
409
|
+
### How It Works
|
|
410
|
+
|
|
411
|
+
1. **First search**: Builds index, saves to `.ccmds-flexsearch/` directory
|
|
412
|
+
2. **Subsequent searches**: Loads cached index if files haven't changed
|
|
413
|
+
3. **Auto-invalidation**: Detects file changes via mtime hashes
|
|
414
|
+
|
|
415
|
+
### Configuration
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"index": {
|
|
420
|
+
"enabled": true,
|
|
421
|
+
"path": ".ccmds-flexsearch/",
|
|
422
|
+
"autoRebuild": true
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
| Option | Default | Description |
|
|
428
|
+
|--------|---------|-------------|
|
|
429
|
+
| `enabled` | `true` | Enable index caching |
|
|
430
|
+
| `path` | `".ccmds-flexsearch/"` | Index directory location |
|
|
431
|
+
| `autoRebuild` | `true` | Automatically rebuild when files change |
|
|
432
|
+
|
|
433
|
+
### Index Commands
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
# View index statistics
|
|
437
|
+
ccmds index stats
|
|
438
|
+
|
|
439
|
+
# Clear cached index
|
|
440
|
+
ccmds index clear
|
|
441
|
+
|
|
442
|
+
# Force rebuild index
|
|
443
|
+
ccmds index rebuild
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Force Rebuild on Search
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
ccmds find "query" --rebuild-index
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Clear Cache Before Search
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
ccmds find "query" --clear-cache
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Query Operators
|
|
459
|
+
|
|
460
|
+
FlexSearch supports extended query operators:
|
|
461
|
+
|
|
462
|
+
| Operator | Example | Description |
|
|
463
|
+
|----------|---------|-------------|
|
|
464
|
+
| (default) | `auth` | Fuzzy/prefix match |
|
|
465
|
+
| `'term` | `'authentication` | Exact substring match |
|
|
466
|
+
| `!term` | `!deprecated` | Exclude results containing term |
|
|
467
|
+
| space | `auth api` | AND search (both terms required) |
|
|
468
|
+
|
|
469
|
+
### Files Created
|
|
470
|
+
|
|
471
|
+
- `.ccmds-flexsearch/` - FlexSearch index directory containing:
|
|
472
|
+
- `meta.json` - Metadata with version, timestamp, and file hashes
|
|
473
|
+
- Multiple `.json` segment files - Indexed document data
|
|
474
|
+
|
|
475
|
+
Add to `.gitignore`:
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
.ccmds-flexsearch/
|
|
479
|
+
.ccmds-cache.json
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
385
484
|
## Context Efficiency Features
|
|
386
485
|
|
|
387
486
|
The CLI includes optimizations to reduce AI context usage by 30-50%:
|
|
@@ -392,10 +491,12 @@ The CLI includes optimizations to reduce AI context usage by 30-50%:
|
|
|
392
491
|
- **Heading paths** - Shows `## Setup > ### Prerequisites` for each match
|
|
393
492
|
- **Deduplication** - Overlapping matches are merged
|
|
394
493
|
|
|
395
|
-
###
|
|
396
|
-
-
|
|
397
|
-
-
|
|
398
|
-
-
|
|
494
|
+
### Context-Aware Previews (find)
|
|
495
|
+
- **Shows the actual paragraph or code block** where the search term appears
|
|
496
|
+
- Uses smart boundary detection (blank lines, headings, code fences)
|
|
497
|
+
- Preserves complete code blocks when match is inside one
|
|
498
|
+
- Configurable line limit via `preview.maxLines` (default: 20)
|
|
499
|
+
- Falls back to file start when no body match (uses `preview.topResults`/`midResults`/`otherResults`)
|
|
399
500
|
|
|
400
501
|
### Frontmatter Filtering
|
|
401
502
|
Only includes useful fields by default: `title`, `description`, `tags`, `category`, `summary`, `keywords`
|
package/README.md
CHANGED
|
@@ -34,18 +34,10 @@ npm i -g cc-md-search-cli
|
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
cd your-project
|
|
37
|
-
ccmds init
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
This creates a `.ccmdsrc` file with `documentDirectories` pointing to your docs folder. **This is the key setting** - it tells ccmds where your documentation lives:
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{
|
|
44
|
-
"documentDirectories": ["./docs"]
|
|
45
|
-
}
|
|
37
|
+
ccmds init
|
|
46
38
|
```
|
|
47
39
|
|
|
48
|
-
|
|
40
|
+
This runs an interactive wizard that creates a `.ccmdsrc` configuration file in the current directory. It guides you through setting up your documentation directories and search preferences.
|
|
49
41
|
|
|
50
42
|
#### Project-Level Agent Instructions
|
|
51
43
|
|
|
@@ -133,6 +125,7 @@ The AI uses these commands behind the scenes when you ask questions like:
|
|
|
133
125
|
| `ccmds docs` | List all configured documentations |
|
|
134
126
|
| `ccmds config` | Show current configuration |
|
|
135
127
|
| `ccmds init` | Create configuration file |
|
|
128
|
+
| `ccmds update` | Update to latest version |
|
|
136
129
|
|
|
137
130
|
**Common options:**
|
|
138
131
|
|
|
@@ -148,7 +141,9 @@ See `ccmds --help` or `ccmds <command> --help` for full options.
|
|
|
148
141
|
Create a `.ccmdsrc` file in your project root:
|
|
149
142
|
|
|
150
143
|
```bash
|
|
151
|
-
ccmds init
|
|
144
|
+
ccmds init # Interactive wizard (in terminal)
|
|
145
|
+
ccmds init -d ./docs # Pre-fill directories
|
|
146
|
+
ccmds init --no-interactive # Skip wizard, use defaults
|
|
152
147
|
```
|
|
153
148
|
|
|
154
149
|
Example configuration:
|
|
@@ -209,6 +204,28 @@ bun install
|
|
|
209
204
|
npm link
|
|
210
205
|
```
|
|
211
206
|
|
|
207
|
+
## Updating
|
|
208
|
+
|
|
209
|
+
Check for and install updates:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Check if update available
|
|
213
|
+
ccmds update --check
|
|
214
|
+
|
|
215
|
+
# Update to latest version
|
|
216
|
+
ccmds update
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Or update manually:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Using Bun
|
|
223
|
+
bun update -g cc-md-search-cli
|
|
224
|
+
|
|
225
|
+
# Using npm
|
|
226
|
+
npm update -g cc-md-search-cli
|
|
227
|
+
```
|
|
228
|
+
|
|
212
229
|
## AI Assistant Integration
|
|
213
230
|
|
|
214
231
|
### Claude Code
|
package/bun.lock
CHANGED
|
@@ -5,35 +5,127 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "cc-md-search-cli",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"@inquirer/prompts": "^8.2.0",
|
|
9
|
+
"chalk": "^5.6.2",
|
|
8
10
|
"commander": "^11.1.0",
|
|
9
|
-
"
|
|
11
|
+
"flexsearch": "^0.8.0",
|
|
10
12
|
"gray-matter": "^4.0.3",
|
|
13
|
+
"ora": "^9.1.0",
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@inquirer/testing": "^3.0.4",
|
|
11
17
|
},
|
|
12
18
|
},
|
|
13
19
|
},
|
|
14
20
|
"packages": {
|
|
21
|
+
"@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="],
|
|
22
|
+
|
|
23
|
+
"@inquirer/checkbox": ["@inquirer/checkbox@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg=="],
|
|
24
|
+
|
|
25
|
+
"@inquirer/confirm": ["@inquirer/confirm@6.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA=="],
|
|
26
|
+
|
|
27
|
+
"@inquirer/core": ["@inquirer/core@11.1.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^9.0.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA=="],
|
|
28
|
+
|
|
29
|
+
"@inquirer/editor": ["@inquirer/editor@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/external-editor": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw=="],
|
|
30
|
+
|
|
31
|
+
"@inquirer/expand": ["@inquirer/expand@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg=="],
|
|
32
|
+
|
|
33
|
+
"@inquirer/external-editor": ["@inquirer/external-editor@2.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w=="],
|
|
34
|
+
|
|
35
|
+
"@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
|
|
36
|
+
|
|
37
|
+
"@inquirer/input": ["@inquirer/input@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw=="],
|
|
38
|
+
|
|
39
|
+
"@inquirer/number": ["@inquirer/number@4.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ=="],
|
|
40
|
+
|
|
41
|
+
"@inquirer/password": ["@inquirer/password@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg=="],
|
|
42
|
+
|
|
43
|
+
"@inquirer/prompts": ["@inquirer/prompts@8.2.0", "", { "dependencies": { "@inquirer/checkbox": "^5.0.4", "@inquirer/confirm": "^6.0.4", "@inquirer/editor": "^5.0.4", "@inquirer/expand": "^5.0.4", "@inquirer/input": "^5.0.4", "@inquirer/number": "^4.0.4", "@inquirer/password": "^5.0.4", "@inquirer/rawlist": "^5.2.0", "@inquirer/search": "^4.1.0", "@inquirer/select": "^5.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA=="],
|
|
44
|
+
|
|
45
|
+
"@inquirer/rawlist": ["@inquirer/rawlist@5.2.0", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg=="],
|
|
46
|
+
|
|
47
|
+
"@inquirer/search": ["@inquirer/search@4.1.0", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA=="],
|
|
48
|
+
|
|
49
|
+
"@inquirer/select": ["@inquirer/select@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g=="],
|
|
50
|
+
|
|
51
|
+
"@inquirer/testing": ["@inquirer/testing@3.0.4", "", { "dependencies": { "@inquirer/type": "^4.0.3", "mute-stream": "^3.0.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-iSx1+r44QsBWD0o/a3xIM8EoXcyVNncBhZ7cofHbGNtHfJk5X16Ne/gNNYVb0jdYbENSCMH0deWjgJ5P2cjJRg=="],
|
|
52
|
+
|
|
53
|
+
"@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
|
|
54
|
+
|
|
55
|
+
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
|
56
|
+
|
|
57
|
+
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
|
58
|
+
|
|
15
59
|
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
|
16
60
|
|
|
61
|
+
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
|
62
|
+
|
|
63
|
+
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
|
|
64
|
+
|
|
65
|
+
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
|
66
|
+
|
|
67
|
+
"cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
|
|
68
|
+
|
|
69
|
+
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
|
70
|
+
|
|
17
71
|
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
|
18
72
|
|
|
73
|
+
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
|
74
|
+
|
|
19
75
|
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
|
20
76
|
|
|
21
77
|
"extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
|
|
22
78
|
|
|
23
|
-
"
|
|
79
|
+
"flexsearch": ["flexsearch@0.8.212", "", {}, "sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw=="],
|
|
80
|
+
|
|
81
|
+
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
|
24
82
|
|
|
25
83
|
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
|
|
26
84
|
|
|
85
|
+
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
|
86
|
+
|
|
27
87
|
"is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
|
|
28
88
|
|
|
89
|
+
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
|
90
|
+
|
|
91
|
+
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
|
92
|
+
|
|
29
93
|
"js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
|
30
94
|
|
|
31
95
|
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
|
|
32
96
|
|
|
97
|
+
"log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
|
|
98
|
+
|
|
99
|
+
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
|
100
|
+
|
|
101
|
+
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
|
|
102
|
+
|
|
103
|
+
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
|
104
|
+
|
|
105
|
+
"ora": ["ora@9.1.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0" } }, "sha512-53uuLsXHOAJl5zLrUrzY9/kE+uIFEx7iaH4g2BIJQK4LZjY4LpCCYZVKDWIkL+F01wAaCg93duQ1whnK/AmY1A=="],
|
|
106
|
+
|
|
107
|
+
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
|
108
|
+
|
|
109
|
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
|
110
|
+
|
|
33
111
|
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
|
|
34
112
|
|
|
113
|
+
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
|
114
|
+
|
|
35
115
|
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
|
36
116
|
|
|
117
|
+
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
|
|
118
|
+
|
|
119
|
+
"string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
|
|
120
|
+
|
|
121
|
+
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
|
122
|
+
|
|
37
123
|
"strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
|
|
124
|
+
|
|
125
|
+
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
|
126
|
+
|
|
127
|
+
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
|
|
128
|
+
|
|
129
|
+
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
|
38
130
|
}
|
|
39
131
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-md-search-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Claude Code Markdown Search - Efficient documentation search CLI for Claude Code integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,8 +32,14 @@
|
|
|
32
32
|
"url": "https://github.com/bjeber/cc-md-search-cli/issues"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@inquirer/prompts": "^8.2.0",
|
|
36
|
+
"chalk": "^5.6.2",
|
|
35
37
|
"commander": "^11.1.0",
|
|
38
|
+
"flexsearch": "^0.8.0",
|
|
36
39
|
"gray-matter": "^4.0.3",
|
|
37
|
-
"
|
|
40
|
+
"ora": "^9.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@inquirer/testing": "^3.0.4"
|
|
38
44
|
}
|
|
39
45
|
}
|
package/skills/ccmds-config.md
CHANGED
|
@@ -52,6 +52,7 @@ The CLI looks for config files in this order:
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"preview": {
|
|
55
|
+
"maxLines": 20,
|
|
55
56
|
"topResults": 600,
|
|
56
57
|
"midResults": 300,
|
|
57
58
|
"otherResults": 150
|
|
@@ -73,9 +74,10 @@ The CLI looks for config files in this order:
|
|
|
73
74
|
| `limit` | `number` | `10` | Default result limit |
|
|
74
75
|
| `fuzzy.threshold` | `number` | `0.4` | Match threshold (0=exact, 1=loose) |
|
|
75
76
|
| `fuzzy.weights` | `object` | See above | Field weights for scoring |
|
|
76
|
-
| `preview.
|
|
77
|
-
| `preview.
|
|
78
|
-
| `preview.
|
|
77
|
+
| `preview.maxLines` | `number` | `20` | Max lines for context-aware previews |
|
|
78
|
+
| `preview.topResults` | `number` | `600` | Fallback preview chars for top 3 results |
|
|
79
|
+
| `preview.midResults` | `number` | `300` | Fallback preview chars for results 4-7 |
|
|
80
|
+
| `preview.otherResults` | `number` | `150` | Fallback preview chars for remaining |
|
|
79
81
|
| `frontmatterFields` | `string[]` | See above | Frontmatter fields to include |
|
|
80
82
|
| `extensions` | `string[]` | `[".md", ".markdown"]` | File extensions |
|
|
81
83
|
| `cache.enabled` | `boolean` | `false` | Enable result caching |
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search result cache system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
|
|
9
|
+
const CACHE_FILE = '.ccmds-cache.json';
|
|
10
|
+
const CACHE_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a cache key from command and options
|
|
14
|
+
* @param {string} command - Command name
|
|
15
|
+
* @param {object} params - Parameters to include in key
|
|
16
|
+
* @returns {string} - Cache key hash
|
|
17
|
+
*/
|
|
18
|
+
export function generateCacheKey(command, params) {
|
|
19
|
+
const keyData = JSON.stringify({ command, ...params });
|
|
20
|
+
return createHash('md5').update(keyData).digest('hex').substring(0, 12);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get cache file path (in project root or config directory)
|
|
25
|
+
* @param {object} config - Configuration object
|
|
26
|
+
* @returns {string} - Cache file path
|
|
27
|
+
*/
|
|
28
|
+
export function getCacheFilePath(config) {
|
|
29
|
+
const baseDir = config._configDir || process.cwd();
|
|
30
|
+
return join(baseDir, CACHE_FILE);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read cache from file
|
|
35
|
+
* @param {string} cachePath - Path to cache file
|
|
36
|
+
* @returns {object} - Cache data or empty cache structure
|
|
37
|
+
*/
|
|
38
|
+
export function readCache(cachePath) {
|
|
39
|
+
try {
|
|
40
|
+
if (existsSync(cachePath)) {
|
|
41
|
+
const data = JSON.parse(readFileSync(cachePath, 'utf-8'));
|
|
42
|
+
if (data.version === CACHE_VERSION) {
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Ignore corrupted cache
|
|
48
|
+
}
|
|
49
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Write cache to file
|
|
54
|
+
* @param {string} cachePath - Path to cache file
|
|
55
|
+
* @param {object} cache - Cache data
|
|
56
|
+
*/
|
|
57
|
+
export function writeCache(cachePath, cache) {
|
|
58
|
+
try {
|
|
59
|
+
writeFileSync(cachePath, JSON.stringify(cache));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
// Ignore write errors
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get cached result if valid
|
|
67
|
+
* @param {object} config - Configuration object
|
|
68
|
+
* @param {string} cacheKey - Cache key
|
|
69
|
+
* @returns {object|null} - Cached results or null
|
|
70
|
+
*/
|
|
71
|
+
export function getCachedResult(config, cacheKey) {
|
|
72
|
+
if (!config.cache?.enabled) return null;
|
|
73
|
+
|
|
74
|
+
const cachePath = getCacheFilePath(config);
|
|
75
|
+
const cache = readCache(cachePath);
|
|
76
|
+
const entry = cache.entries[cacheKey];
|
|
77
|
+
|
|
78
|
+
if (!entry) return null;
|
|
79
|
+
|
|
80
|
+
// Check TTL
|
|
81
|
+
const ttl = config.cache.ttl || 300;
|
|
82
|
+
const age = (Date.now() - entry.created) / 1000;
|
|
83
|
+
if (age > ttl) {
|
|
84
|
+
// Expired - remove entry
|
|
85
|
+
delete cache.entries[cacheKey];
|
|
86
|
+
writeCache(cachePath, cache);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return entry.results;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Store result in cache
|
|
95
|
+
* @param {object} config - Configuration object
|
|
96
|
+
* @param {string} cacheKey - Cache key
|
|
97
|
+
* @param {string} command - Command name
|
|
98
|
+
* @param {any} results - Results to cache
|
|
99
|
+
*/
|
|
100
|
+
export function setCachedResult(config, cacheKey, command, results) {
|
|
101
|
+
if (!config.cache?.enabled) return;
|
|
102
|
+
|
|
103
|
+
const cachePath = getCacheFilePath(config);
|
|
104
|
+
const cache = readCache(cachePath);
|
|
105
|
+
|
|
106
|
+
// Add new entry
|
|
107
|
+
cache.entries[cacheKey] = {
|
|
108
|
+
created: Date.now(),
|
|
109
|
+
command,
|
|
110
|
+
results,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Trim old entries if over limit
|
|
114
|
+
const maxEntries = config.cache.maxEntries || 50;
|
|
115
|
+
const entries = Object.entries(cache.entries);
|
|
116
|
+
if (entries.length > maxEntries) {
|
|
117
|
+
// Sort by created time, remove oldest
|
|
118
|
+
entries.sort((a, b) => a[1].created - b[1].created);
|
|
119
|
+
const toRemove = entries.slice(0, entries.length - maxEntries);
|
|
120
|
+
toRemove.forEach(([key]) => delete cache.entries[key]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
writeCache(cachePath, cache);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Clear all cache entries
|
|
128
|
+
* @param {object} config - Configuration object
|
|
129
|
+
* @returns {number} - Number of entries cleared
|
|
130
|
+
*/
|
|
131
|
+
export function clearCache(config) {
|
|
132
|
+
const cachePath = getCacheFilePath(config);
|
|
133
|
+
if (existsSync(cachePath)) {
|
|
134
|
+
const cache = readCache(cachePath);
|
|
135
|
+
const count = Object.keys(cache.entries).length;
|
|
136
|
+
unlinkSync(cachePath);
|
|
137
|
+
return count;
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get cache statistics
|
|
144
|
+
* @param {object} config - Configuration object
|
|
145
|
+
* @returns {object} - Cache stats
|
|
146
|
+
*/
|
|
147
|
+
export function getCacheStats(config) {
|
|
148
|
+
const cachePath = getCacheFilePath(config);
|
|
149
|
+
const cache = readCache(cachePath);
|
|
150
|
+
const entries = Object.values(cache.entries);
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const ttl = (config.cache?.ttl || 300) * 1000;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
enabled: config.cache?.enabled || false,
|
|
156
|
+
path: cachePath,
|
|
157
|
+
exists: existsSync(cachePath),
|
|
158
|
+
totalEntries: entries.length,
|
|
159
|
+
validEntries: entries.filter((e) => now - e.created < ttl).length,
|
|
160
|
+
expiredEntries: entries.filter((e) => now - e.created >= ttl).length,
|
|
161
|
+
commands: entries.reduce((acc, e) => {
|
|
162
|
+
acc[e.command] = (acc[e.command] || 0) + 1;
|
|
163
|
+
return acc;
|
|
164
|
+
}, {}),
|
|
165
|
+
};
|
|
166
|
+
}
|