pointfree-docs 0.1.0 → 0.2.0
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 +67 -7
- package/dist/cli.js +9 -1
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +59 -2
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +78 -13
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +59 -19
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +27 -4
- package/dist/commands/stats.js +32 -5
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +33 -7
- package/dist/config.d.ts +32 -1
- package/dist/config.js +34 -1
- package/dist/lib/format.d.ts +12 -0
- package/dist/lib/format.js +30 -0
- package/dist/lib/index.d.ts +14 -2
- package/dist/lib/index.js +162 -25
- package/dist/lib/repos.d.ts +32 -0
- package/dist/lib/repos.js +133 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI tool for searching Point-Free library documentation locally. Uses sparse git checkout and SQLite FTS5 for fast, offline full-text search. Built for use with AI coding assistants like Claude Code.
|
|
4
4
|
|
|
5
|
+
## Demo
|
|
6
|
+
|
|
7
|
+
See pf-docs in action: install, search, and real-world usage with Claude Code.
|
|
8
|
+
|
|
9
|
+
https://github.com/user-attachments/assets/ed6c6340-2d29-4515-89ca-62f65cfa4755
|
|
10
|
+
|
|
5
11
|
## Quick Start
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -31,7 +37,7 @@ pf-docs list tca
|
|
|
31
37
|
|
|
32
38
|
### `pf-docs list --available`
|
|
33
39
|
|
|
34
|
-
Show all libraries available to download.
|
|
40
|
+
Show all libraries available to download, plus additional sources (examples, episodes).
|
|
35
41
|
|
|
36
42
|
### `pf-docs init`
|
|
37
43
|
|
|
@@ -40,6 +46,9 @@ Download and index documentation. Only fetches `Documentation.docc` folders via
|
|
|
40
46
|
```bash
|
|
41
47
|
pf-docs init --libs tca dependencies navigation
|
|
42
48
|
pf-docs init --all
|
|
49
|
+
pf-docs init --examples # TCA CaseStudies, SyncUps, etc.
|
|
50
|
+
pf-docs init --episodes # 350+ Point-Free episode code samples
|
|
51
|
+
pf-docs init --all --examples --episodes # Everything
|
|
43
52
|
```
|
|
44
53
|
|
|
45
54
|
### `pf-docs update`
|
|
@@ -47,8 +56,10 @@ pf-docs init --all
|
|
|
47
56
|
Pull latest changes and re-index.
|
|
48
57
|
|
|
49
58
|
```bash
|
|
50
|
-
pf-docs update
|
|
51
|
-
pf-docs update --libs tca
|
|
59
|
+
pf-docs update # All initialized libraries
|
|
60
|
+
pf-docs update --libs tca # Specific libraries
|
|
61
|
+
pf-docs update --examples # Update examples
|
|
62
|
+
pf-docs update --episodes # Update episodes
|
|
52
63
|
```
|
|
53
64
|
|
|
54
65
|
### `pf-docs search <query>`
|
|
@@ -59,6 +70,12 @@ Full-text search across all indexed docs.
|
|
|
59
70
|
pf-docs search "testing effects"
|
|
60
71
|
pf-docs search "navigation" --lib tca
|
|
61
72
|
pf-docs search "Store" --limit 5
|
|
73
|
+
|
|
74
|
+
# Search by source type
|
|
75
|
+
pf-docs search "TestStore" --source=docs # Default: docs only
|
|
76
|
+
pf-docs search "TestStore" --source=examples # TCA examples only
|
|
77
|
+
pf-docs search "TestStore" --source=episodes # Episode code only
|
|
78
|
+
pf-docs search "TestStore" --source=all # Everything, labeled
|
|
62
79
|
```
|
|
63
80
|
|
|
64
81
|
### `pf-docs get <path>`
|
|
@@ -68,6 +85,11 @@ Fetch a specific article as clean markdown.
|
|
|
68
85
|
```bash
|
|
69
86
|
pf-docs get tca/Articles/TestingTCA
|
|
70
87
|
pf-docs get dependencies/Articles/QuickStart --raw
|
|
88
|
+
|
|
89
|
+
# Code files show preview by default (50 lines)
|
|
90
|
+
pf-docs get examples/CaseStudies/03-Effects-Basics.swift
|
|
91
|
+
pf-docs get examples/CaseStudies/03-Effects-Basics.swift --raw # Full content
|
|
92
|
+
pf-docs get episodes/0156-testable-state/Main.swift --lines=100 # More lines
|
|
71
93
|
```
|
|
72
94
|
|
|
73
95
|
### `pf-docs list [lib]`
|
|
@@ -75,13 +97,15 @@ pf-docs get dependencies/Articles/QuickStart --raw
|
|
|
75
97
|
List indexed documentation.
|
|
76
98
|
|
|
77
99
|
```bash
|
|
78
|
-
pf-docs list
|
|
79
|
-
pf-docs list tca --tree
|
|
100
|
+
pf-docs list # All indexed docs
|
|
101
|
+
pf-docs list tca --tree # Tree view for one library
|
|
102
|
+
pf-docs list --source=examples # Only examples
|
|
103
|
+
pf-docs list --source=all # All sources
|
|
80
104
|
```
|
|
81
105
|
|
|
82
106
|
### `pf-docs stats`
|
|
83
107
|
|
|
84
|
-
Show indexing statistics.
|
|
108
|
+
Show indexing statistics by source and library.
|
|
85
109
|
|
|
86
110
|
All commands support `--json` for programmatic output.
|
|
87
111
|
|
|
@@ -104,12 +128,48 @@ All commands support `--json` for programmatic output.
|
|
|
104
128
|
|
|
105
129
|
Run `pf-docs list --available` to see this list in your terminal.
|
|
106
130
|
|
|
131
|
+
## Additional Sources
|
|
132
|
+
|
|
133
|
+
| Source | Description | Flag |
|
|
134
|
+
|--------|-------------|------|
|
|
135
|
+
| `examples` | TCA CaseStudies, SyncUps, Todos, VoiceMemos, etc. | `--examples` |
|
|
136
|
+
| `episodes` | 350+ Point-Free episode code samples | `--episodes` |
|
|
137
|
+
|
|
138
|
+
### Source Types for Search
|
|
139
|
+
|
|
140
|
+
When searching, you can filter by source type:
|
|
141
|
+
|
|
142
|
+
- `docs` (default) — Library documentation (DocC articles)
|
|
143
|
+
- `examples` — TCA example apps and case studies
|
|
144
|
+
- `episodes` — Point-Free episode code samples
|
|
145
|
+
- `all` — Everything, with labels showing the source
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Search docs for concepts
|
|
149
|
+
pf-docs search "testing effects"
|
|
150
|
+
|
|
151
|
+
# Search examples for real implementations
|
|
152
|
+
pf-docs search "TestStore" --source=examples
|
|
153
|
+
|
|
154
|
+
# Search everything
|
|
155
|
+
pf-docs search "dependency injection" --source=all
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Results with `--source=all` are labeled:
|
|
159
|
+
```
|
|
160
|
+
[DOC] tca/Articles/Testing (0.94)
|
|
161
|
+
[EXAMPLE] CaseStudies/03-Effects-Basics (0.82)
|
|
162
|
+
[EPISODE] Ep156: Testable State (0.71)
|
|
163
|
+
```
|
|
164
|
+
|
|
107
165
|
## Usage with Claude Code
|
|
108
166
|
|
|
109
167
|
Add the key commands to your project's `CLAUDE.md`:
|
|
110
168
|
|
|
111
169
|
```markdown
|
|
112
|
-
Use `pf-docs search "<query>"` to search Point-Free docs
|
|
170
|
+
Use `pf-docs search "<query>"` to search Point-Free docs.
|
|
171
|
+
Use `pf-docs search "<query>" --source=examples` to find real code examples.
|
|
172
|
+
Use `pf-docs get <path>` to read an article or code file.
|
|
113
173
|
```
|
|
114
174
|
|
|
115
175
|
## Adding Libraries
|
package/dist/cli.js
CHANGED
|
@@ -15,23 +15,28 @@ const program = new Command();
|
|
|
15
15
|
program
|
|
16
16
|
.name("pf-docs")
|
|
17
17
|
.description("CLI tool for searching Point-Free library documentation")
|
|
18
|
-
.version("0.
|
|
18
|
+
.version("0.2.0");
|
|
19
19
|
program
|
|
20
20
|
.command("init")
|
|
21
21
|
.description("Initialize and download documentation for specified libraries")
|
|
22
22
|
.option("-l, --libs <libs...>", "Libraries to download (e.g., tca dependencies)")
|
|
23
23
|
.option("-a, --all", "Download all available libraries")
|
|
24
|
+
.option("-e, --examples", "Download TCA examples (CaseStudies, SyncUps, etc.)")
|
|
25
|
+
.option("-p, --episodes", "Download Point-Free episode code samples (350+)")
|
|
24
26
|
.action(initCommand);
|
|
25
27
|
program
|
|
26
28
|
.command("update")
|
|
27
29
|
.description("Update documentation from remote repositories")
|
|
28
30
|
.option("-l, --libs <libs...>", "Specific libraries to update")
|
|
31
|
+
.option("-e, --examples", "Update examples")
|
|
32
|
+
.option("-p, --episodes", "Update episodes")
|
|
29
33
|
.action(updateCommand);
|
|
30
34
|
program
|
|
31
35
|
.command("search <query>")
|
|
32
36
|
.description("Search across all indexed documentation")
|
|
33
37
|
.option("-l, --lib <lib>", "Limit search to specific library")
|
|
34
38
|
.option("-n, --limit <n>", "Max results to return", "10")
|
|
39
|
+
.option("-s, --source <source>", "Source type: docs, examples, episodes, all (default: docs)")
|
|
35
40
|
.option("-j, --json", "Output results as JSON")
|
|
36
41
|
.action(searchCommand);
|
|
37
42
|
program
|
|
@@ -39,6 +44,8 @@ program
|
|
|
39
44
|
.description("Get a specific documentation article (e.g., tca/Testing)")
|
|
40
45
|
.option("-j, --json", "Output as JSON")
|
|
41
46
|
.option("-r, --raw", "Output raw content without header")
|
|
47
|
+
.option("-p, --preview", "Preview mode (first 50 lines for code files)")
|
|
48
|
+
.option("--lines <n>", "Number of lines to show in preview mode", "50")
|
|
42
49
|
.action(getCommand);
|
|
43
50
|
program
|
|
44
51
|
.command("list [lib]")
|
|
@@ -46,6 +53,7 @@ program
|
|
|
46
53
|
.option("-t, --tree", "Show as tree structure")
|
|
47
54
|
.option("-j, --json", "Output as JSON")
|
|
48
55
|
.option("-a, --available", "Show all libraries available to download")
|
|
56
|
+
.option("-s, --source <source>", "Filter by source: docs, examples, episodes, all")
|
|
49
57
|
.action(listCommand);
|
|
50
58
|
program
|
|
51
59
|
.command("stats")
|
package/dist/commands/get.d.ts
CHANGED
package/dist/commands/get.js
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { getDoc, withIndex } from "../lib/index.js";
|
|
6
6
|
import { formatDocForOutput } from "../lib/markdown.js";
|
|
7
|
+
import { getSourceLabel } from "../lib/format.js";
|
|
8
|
+
const DEFAULT_PREVIEW_LINES = 50;
|
|
9
|
+
/**
|
|
10
|
+
* Truncate content to a specified number of lines
|
|
11
|
+
*/
|
|
12
|
+
function truncateContent(content, maxLines) {
|
|
13
|
+
const lines = content.split("\n");
|
|
14
|
+
const totalLines = lines.length;
|
|
15
|
+
if (lines.length <= maxLines) {
|
|
16
|
+
return { content, truncated: false, totalLines };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
content: lines.slice(0, maxLines).join("\n"),
|
|
20
|
+
truncated: true,
|
|
21
|
+
totalLines,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a document is code (not documentation) based on its source
|
|
26
|
+
*/
|
|
27
|
+
function isCodeFile(doc) {
|
|
28
|
+
return doc.source === "examples" || doc.source === "episodes";
|
|
29
|
+
}
|
|
7
30
|
export function getCommand(path, options = {}) {
|
|
8
31
|
const doc = withIndex(() => getDoc(path));
|
|
9
32
|
if (!doc) {
|
|
@@ -16,8 +39,28 @@ export function getCommand(path, options = {}) {
|
|
|
16
39
|
console.log(chalk.gray(`Or search: pf-docs search "<query>"`));
|
|
17
40
|
return;
|
|
18
41
|
}
|
|
42
|
+
// Determine preview lines
|
|
43
|
+
const previewLines = options.lines
|
|
44
|
+
? parseInt(options.lines, 10)
|
|
45
|
+
: DEFAULT_PREVIEW_LINES;
|
|
46
|
+
// Apply preview mode for code files (examples/episodes)
|
|
47
|
+
const shouldPreview = options.preview || (isCodeFile(doc) && !options.raw);
|
|
48
|
+
let outputContent = doc.content;
|
|
49
|
+
let truncationInfo = "";
|
|
50
|
+
if (shouldPreview && !options.raw) {
|
|
51
|
+
const { content, truncated, totalLines } = truncateContent(doc.content, previewLines);
|
|
52
|
+
outputContent = content;
|
|
53
|
+
if (truncated) {
|
|
54
|
+
truncationInfo = `\n${chalk.gray(`... truncated (showing ${previewLines} of ${totalLines} lines)`)}\n${chalk.gray(`Use --raw for full content, or --lines=<n> for more`)}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
19
57
|
if (options.json) {
|
|
20
|
-
|
|
58
|
+
const result = { ...doc, content: outputContent };
|
|
59
|
+
if (truncationInfo) {
|
|
60
|
+
result.truncated = true;
|
|
61
|
+
result.previewLines = previewLines;
|
|
62
|
+
}
|
|
63
|
+
console.log(JSON.stringify(result, null, 2));
|
|
21
64
|
return;
|
|
22
65
|
}
|
|
23
66
|
// Raw output (just content, no header)
|
|
@@ -25,6 +68,20 @@ export function getCommand(path, options = {}) {
|
|
|
25
68
|
console.log(doc.content);
|
|
26
69
|
return;
|
|
27
70
|
}
|
|
71
|
+
// For code files, show with syntax hint
|
|
72
|
+
if (isCodeFile(doc)) {
|
|
73
|
+
console.log(`\n${getSourceLabel(doc.source)} ${chalk.bold(doc.title)}`);
|
|
74
|
+
console.log(chalk.gray(`Path: ${doc.path}`));
|
|
75
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
76
|
+
console.log(outputContent);
|
|
77
|
+
if (truncationInfo) {
|
|
78
|
+
console.log(truncationInfo);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
28
82
|
// Output the formatted document (clean markdown)
|
|
29
|
-
console.log(formatDocForOutput(doc));
|
|
83
|
+
console.log(formatDocForOutput({ ...doc, content: outputContent }));
|
|
84
|
+
if (truncationInfo) {
|
|
85
|
+
console.log(truncationInfo);
|
|
86
|
+
}
|
|
30
87
|
}
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { LIBRARIES, getLibrary, LIBRARY_NAMES } from "../config.js";
|
|
6
|
-
import { cloneLibrary } from "../lib/repos.js";
|
|
7
|
-
import { indexLibrary, openIndex, closeIndex } from "../lib/index.js";
|
|
6
|
+
import { cloneLibrary, cloneExamples, cloneEpisodes } from "../lib/repos.js";
|
|
7
|
+
import { indexLibrary, indexExamples, indexEpisodes, openIndex, closeIndex } from "../lib/index.js";
|
|
8
8
|
export async function initCommand(options) {
|
|
9
9
|
console.log(chalk.bold("\n📚 Initializing Point-Free Documentation\n"));
|
|
10
|
+
const includeExamples = options.examples || false;
|
|
11
|
+
const includeEpisodes = options.episodes || false;
|
|
10
12
|
// Determine which libraries to download
|
|
11
13
|
let librariesToInit = LIBRARIES;
|
|
12
14
|
if (options.libs && !options.all) {
|
|
@@ -22,28 +24,67 @@ export async function initCommand(options) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.log(
|
|
28
|
-
console.log(
|
|
27
|
+
// If no libs specified and not --all, and no examples/episodes, show usage
|
|
28
|
+
if (librariesToInit.length === 0 && !includeExamples && !includeEpisodes) {
|
|
29
|
+
console.log(chalk.red("No valid libraries or sources specified."));
|
|
30
|
+
console.log(`\nUsage:`);
|
|
31
|
+
console.log(` pf-docs init --libs tca dependencies navigation`);
|
|
32
|
+
console.log(` pf-docs init --all`);
|
|
33
|
+
console.log(` pf-docs init --examples`);
|
|
34
|
+
console.log(` pf-docs init --episodes`);
|
|
35
|
+
console.log(` pf-docs init --all --examples --episodes`);
|
|
29
36
|
console.log(`\nAvailable libraries: ${LIBRARY_NAMES.join(", ")}`);
|
|
30
37
|
return;
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
// Show what we're going to initialize
|
|
40
|
+
const sources = [];
|
|
41
|
+
if (librariesToInit.length > 0) {
|
|
42
|
+
sources.push(`Docs: ${librariesToInit.map((l) => l.shortName).join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
if (includeExamples) {
|
|
45
|
+
sources.push("Examples (TCA CaseStudies, SyncUps, etc.)");
|
|
46
|
+
}
|
|
47
|
+
if (includeEpisodes) {
|
|
48
|
+
sources.push("Episodes (350+ Point-Free episode code samples)");
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.blue(`Initializing: \n ${sources.join("\n ")}\n`));
|
|
51
|
+
// Clone library documentation repositories
|
|
52
|
+
if (librariesToInit.length > 0) {
|
|
53
|
+
console.log(chalk.bold("Cloning documentation repositories..."));
|
|
54
|
+
for (const lib of librariesToInit) {
|
|
55
|
+
try {
|
|
56
|
+
await cloneLibrary(lib);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(chalk.red(` ✗ Failed to clone ${lib.shortName}:`), error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Clone examples if requested
|
|
64
|
+
if (includeExamples) {
|
|
65
|
+
console.log(chalk.bold("\nCloning TCA examples..."));
|
|
66
|
+
try {
|
|
67
|
+
await cloneExamples();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(chalk.red(` ✗ Failed to clone examples:`), error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Clone episodes if requested
|
|
74
|
+
if (includeEpisodes) {
|
|
75
|
+
console.log(chalk.bold("\nCloning episode code samples..."));
|
|
36
76
|
try {
|
|
37
|
-
await
|
|
77
|
+
await cloneEpisodes();
|
|
38
78
|
}
|
|
39
79
|
catch (error) {
|
|
40
|
-
console.error(chalk.red(` ✗ Failed to clone
|
|
80
|
+
console.error(chalk.red(` ✗ Failed to clone episodes:`), error);
|
|
41
81
|
}
|
|
42
82
|
}
|
|
43
83
|
// Build search index
|
|
44
84
|
console.log(chalk.bold("\nBuilding search index..."));
|
|
45
85
|
openIndex();
|
|
46
86
|
let totalIndexed = 0;
|
|
87
|
+
// Index library docs
|
|
47
88
|
for (const lib of librariesToInit) {
|
|
48
89
|
try {
|
|
49
90
|
const count = indexLibrary(lib);
|
|
@@ -54,10 +95,34 @@ export async function initCommand(options) {
|
|
|
54
95
|
console.error(chalk.red(` ✗ Failed to index ${lib.shortName}:`), error);
|
|
55
96
|
}
|
|
56
97
|
}
|
|
98
|
+
// Index examples
|
|
99
|
+
if (includeExamples) {
|
|
100
|
+
try {
|
|
101
|
+
const count = indexExamples();
|
|
102
|
+
console.log(` ✓ Indexed examples: ${count} files`);
|
|
103
|
+
totalIndexed += count;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(chalk.red(` ✗ Failed to index examples:`), error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Index episodes
|
|
110
|
+
if (includeEpisodes) {
|
|
111
|
+
try {
|
|
112
|
+
const count = indexEpisodes();
|
|
113
|
+
console.log(` ✓ Indexed episodes: ${count} files`);
|
|
114
|
+
totalIndexed += count;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(chalk.red(` ✗ Failed to index episodes:`), error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
57
120
|
closeIndex();
|
|
58
|
-
console.log(chalk.green(`\n✅ Done! Indexed ${totalIndexed}
|
|
121
|
+
console.log(chalk.green(`\n✅ Done! Indexed ${totalIndexed} items.\n`));
|
|
59
122
|
console.log(`Try it out:`);
|
|
60
123
|
console.log(chalk.gray(` pf-docs search "testing async effects"`));
|
|
124
|
+
console.log(chalk.gray(` pf-docs search "Store" --source=examples`));
|
|
125
|
+
console.log(chalk.gray(` pf-docs search "dependency" --source=all`));
|
|
61
126
|
console.log(chalk.gray(` pf-docs list tca`));
|
|
62
127
|
console.log(chalk.gray(` pf-docs get tca/Testing`));
|
|
63
128
|
}
|
package/dist/commands/list.d.ts
CHANGED
package/dist/commands/list.js
CHANGED
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { listDocs, getStats, withIndex } from "../lib/index.js";
|
|
6
|
-
import { getLibrary, LIBRARIES, LIBRARY_NAMES } from "../config.js";
|
|
6
|
+
import { getLibrary, LIBRARIES, LIBRARY_NAMES, SOURCE_TYPES, EXAMPLES_CONFIG, EPISODES_CONFIG } from "../config.js";
|
|
7
|
+
import { getSourceLabel } from "../lib/format.js";
|
|
7
8
|
export function listCommand(lib, options) {
|
|
8
9
|
if (options.available) {
|
|
9
10
|
if (options.json) {
|
|
10
11
|
const libs = LIBRARIES.map(({ shortName, name, repo, description }) => ({
|
|
11
12
|
shortName, name, repo, description,
|
|
12
13
|
}));
|
|
13
|
-
|
|
14
|
+
const extras = [
|
|
15
|
+
{ shortName: "examples", name: EXAMPLES_CONFIG.name, repo: EXAMPLES_CONFIG.repo, description: EXAMPLES_CONFIG.description },
|
|
16
|
+
{ shortName: "episodes", name: EPISODES_CONFIG.name, repo: EPISODES_CONFIG.repo, description: EPISODES_CONFIG.description },
|
|
17
|
+
];
|
|
18
|
+
console.log(JSON.stringify({ libraries: libs, extras }, null, 2));
|
|
14
19
|
return;
|
|
15
20
|
}
|
|
16
21
|
console.log(chalk.bold(`\n📦 Available Libraries\n`));
|
|
@@ -20,10 +25,27 @@ export function listCommand(lib, options) {
|
|
|
20
25
|
}
|
|
21
26
|
console.log(chalk.gray(`Total: ${LIBRARIES.length} libraries`));
|
|
22
27
|
console.log(chalk.gray(`\nTo download: pf-docs init --libs <name> [<name>...]`));
|
|
28
|
+
console.log(chalk.bold(`\n📦 Additional Sources\n`));
|
|
29
|
+
console.log(` ${chalk.magenta("examples".padEnd(24))} ${EXAMPLES_CONFIG.description}`);
|
|
30
|
+
console.log(chalk.gray(` ${"".padEnd(24)} --examples flag\n`));
|
|
31
|
+
console.log(` ${chalk.yellow("episodes".padEnd(24))} ${EPISODES_CONFIG.description}`);
|
|
32
|
+
console.log(chalk.gray(` ${"".padEnd(24)} --episodes flag\n`));
|
|
23
33
|
return;
|
|
24
34
|
}
|
|
35
|
+
// Validate source option
|
|
36
|
+
let source;
|
|
37
|
+
if (options.source) {
|
|
38
|
+
if (options.source === "all" || SOURCE_TYPES.includes(options.source)) {
|
|
39
|
+
source = options.source;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(chalk.red(`Invalid source: ${options.source}`));
|
|
43
|
+
console.log(chalk.gray(`Valid sources: ${SOURCE_TYPES.join(", ")}, all`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
25
47
|
const { docs, stats } = withIndex(() => ({
|
|
26
|
-
docs: listDocs(lib),
|
|
48
|
+
docs: listDocs(lib, source),
|
|
27
49
|
stats: getStats(),
|
|
28
50
|
}));
|
|
29
51
|
// JSON output for programmatic use
|
|
@@ -45,6 +67,11 @@ export function listCommand(lib, options) {
|
|
|
45
67
|
console.log(chalk.bold(`\n📄 Available Documentation\n`));
|
|
46
68
|
// Show stats summary
|
|
47
69
|
if (!lib) {
|
|
70
|
+
console.log(chalk.gray(`Indexed by source:`));
|
|
71
|
+
for (const [sourceName, count] of Object.entries(stats.bySource)) {
|
|
72
|
+
console.log(chalk.gray(` ${getSourceLabel(sourceName)}: ${count} items`));
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
48
75
|
console.log(chalk.gray(`Indexed libraries:`));
|
|
49
76
|
for (const [libName, count] of Object.entries(stats.byLibrary)) {
|
|
50
77
|
const libConfig = getLibrary(libName);
|
|
@@ -54,33 +81,46 @@ export function listCommand(lib, options) {
|
|
|
54
81
|
console.log();
|
|
55
82
|
}
|
|
56
83
|
if (options.tree) {
|
|
57
|
-
// Group by
|
|
58
|
-
const
|
|
84
|
+
// Group by source and library, show as tree
|
|
85
|
+
const bySource = new Map();
|
|
59
86
|
for (const doc of docs) {
|
|
60
|
-
if (!
|
|
61
|
-
|
|
87
|
+
if (!bySource.has(doc.source)) {
|
|
88
|
+
bySource.set(doc.source, new Map());
|
|
89
|
+
}
|
|
90
|
+
const sourceMap = bySource.get(doc.source);
|
|
91
|
+
if (!sourceMap.has(doc.library)) {
|
|
92
|
+
sourceMap.set(doc.library, []);
|
|
62
93
|
}
|
|
63
|
-
|
|
94
|
+
sourceMap.get(doc.library).push(doc);
|
|
64
95
|
}
|
|
65
|
-
for (const [
|
|
66
|
-
console.log(
|
|
67
|
-
for (
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
96
|
+
for (const [sourceName, byLibrary] of bySource) {
|
|
97
|
+
console.log(getSourceLabel(sourceName));
|
|
98
|
+
for (const [library, libraryDocs] of byLibrary) {
|
|
99
|
+
console.log(chalk.blue(` ${library}/`));
|
|
100
|
+
const maxToShow = 10;
|
|
101
|
+
for (let i = 0; i < Math.min(libraryDocs.length, maxToShow); i++) {
|
|
102
|
+
const doc = libraryDocs[i];
|
|
103
|
+
const relativePath = doc.path.replace(`${library}/`, "").replace(`${sourceName}/`, "");
|
|
104
|
+
const isLast = i === Math.min(libraryDocs.length, maxToShow) - 1;
|
|
105
|
+
const prefix = isLast ? "└── " : "├── ";
|
|
106
|
+
console.log(chalk.gray(` ${prefix}${relativePath}`));
|
|
107
|
+
}
|
|
108
|
+
if (libraryDocs.length > maxToShow) {
|
|
109
|
+
console.log(chalk.gray(` ... and ${libraryDocs.length - maxToShow} more`));
|
|
110
|
+
}
|
|
73
111
|
}
|
|
74
112
|
console.log();
|
|
75
113
|
}
|
|
76
114
|
}
|
|
77
115
|
else {
|
|
78
|
-
// Simple list
|
|
116
|
+
// Simple list with source labels
|
|
117
|
+
const showSourceLabel = !source || source === "all";
|
|
79
118
|
for (const doc of docs) {
|
|
80
|
-
|
|
119
|
+
const label = showSourceLabel ? `${getSourceLabel(doc.source)} ` : "";
|
|
120
|
+
console.log(`${label}${chalk.blue(doc.path)}`);
|
|
81
121
|
console.log(chalk.gray(` ${doc.title}`));
|
|
82
122
|
}
|
|
83
123
|
}
|
|
84
|
-
console.log(chalk.gray(`\nTotal: ${docs.length}
|
|
124
|
+
console.log(chalk.gray(`\nTotal: ${docs.length} items`));
|
|
85
125
|
console.log(chalk.gray(`\nTo view a document: pf-docs get <path>`));
|
|
86
126
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { search as searchIndex, withIndex } from "../lib/index.js";
|
|
6
|
+
import { SOURCE_TYPES } from "../config.js";
|
|
7
|
+
import { getSourceLabel } from "../lib/format.js";
|
|
6
8
|
export function searchCommand(query, options) {
|
|
7
9
|
const limit = options.limit ? parseInt(options.limit, 10) : 10;
|
|
8
|
-
|
|
10
|
+
// Validate source option
|
|
11
|
+
let source = "docs"; // Default to docs only
|
|
12
|
+
if (options.source) {
|
|
13
|
+
if (options.source === "all" || SOURCE_TYPES.includes(options.source)) {
|
|
14
|
+
source = options.source;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.log(chalk.red(`Invalid source: ${options.source}`));
|
|
18
|
+
console.log(chalk.gray(`Valid sources: ${SOURCE_TYPES.join(", ")}, all`));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const results = withIndex(() => searchIndex(query, { lib: options.lib, limit, source }));
|
|
9
23
|
if (options.json) {
|
|
10
|
-
console.log(JSON.stringify({ query, results }, null, 2));
|
|
24
|
+
console.log(JSON.stringify({ query, source, results }, null, 2));
|
|
11
25
|
return;
|
|
12
26
|
}
|
|
13
27
|
if (results.length === 0) {
|
|
@@ -15,17 +29,26 @@ export function searchCommand(query, options) {
|
|
|
15
29
|
if (options.lib) {
|
|
16
30
|
console.log(chalk.gray(` (searched in library: ${options.lib})`));
|
|
17
31
|
}
|
|
32
|
+
if (source !== "all") {
|
|
33
|
+
console.log(chalk.gray(` (searched in source: ${source})`));
|
|
34
|
+
console.log(chalk.gray(` Try --source=all to search everything`));
|
|
35
|
+
}
|
|
18
36
|
console.log(chalk.gray(`\nTry a different query or run 'pf-docs list' to see available docs.`));
|
|
19
37
|
return;
|
|
20
38
|
}
|
|
21
|
-
|
|
39
|
+
const sourceLabel = source === "all" ? "all sources" : source;
|
|
40
|
+
console.log(chalk.bold(`\n🔍 Search results for: "${query}" (${sourceLabel})\n`));
|
|
22
41
|
for (let i = 0; i < results.length; i++) {
|
|
23
42
|
const result = results[i];
|
|
24
|
-
|
|
43
|
+
const label = source === "all" ? `${getSourceLabel(result.source)} ` : "";
|
|
44
|
+
console.log(`${label}${chalk.blue(`${i + 1}. ${result.title}`)}`);
|
|
25
45
|
console.log(chalk.gray(` Path: ${result.path}`));
|
|
26
46
|
console.log(chalk.gray(` ${result.snippet}`));
|
|
27
47
|
console.log();
|
|
28
48
|
}
|
|
29
49
|
console.log(chalk.gray(`\nTo view a document: pf-docs get <path>`));
|
|
30
50
|
console.log(chalk.gray(`Example: pf-docs get ${results[0].path}`));
|
|
51
|
+
if (source !== "all") {
|
|
52
|
+
console.log(chalk.gray(`\nTip: Use --source=all to search docs, examples, and episodes together`));
|
|
53
|
+
}
|
|
31
54
|
}
|
package/dist/commands/stats.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { getStats, withIndex } from "../lib/index.js";
|
|
6
|
-
import { LIBRARIES, getLibrary } from "../config.js";
|
|
7
|
-
import { isLibraryCloned } from "../lib/repos.js";
|
|
6
|
+
import { LIBRARIES, getLibrary, EXAMPLES_CONFIG, EPISODES_CONFIG } from "../config.js";
|
|
7
|
+
import { isLibraryCloned, areExamplesCloned, areEpisodesCloned } from "../lib/repos.js";
|
|
8
|
+
import { getSourceName } from "../lib/format.js";
|
|
8
9
|
export function statsCommand(options) {
|
|
9
10
|
const stats = withIndex(() => getStats());
|
|
10
11
|
if (options.json) {
|
|
@@ -12,12 +13,19 @@ export function statsCommand(options) {
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
console.log(chalk.bold("\n📊 pf-docs Statistics\n"));
|
|
15
|
-
console.log(chalk.blue(`Total indexed
|
|
16
|
+
console.log(chalk.blue(`Total indexed items: ${stats.totalDocs}`));
|
|
16
17
|
console.log();
|
|
17
|
-
|
|
18
|
+
// Show breakdown by source
|
|
19
|
+
console.log(chalk.bold("By source:"));
|
|
20
|
+
for (const [sourceName, count] of Object.entries(stats.bySource)) {
|
|
21
|
+
console.log(` ${getSourceName(sourceName)}: ${count} items`);
|
|
22
|
+
}
|
|
23
|
+
console.log();
|
|
24
|
+
// Show breakdown by library
|
|
25
|
+
console.log(chalk.bold("By library:"));
|
|
18
26
|
for (const [libName, count] of Object.entries(stats.byLibrary)) {
|
|
19
27
|
const libConfig = getLibrary(libName);
|
|
20
|
-
console.log(` ${chalk.green("●")} ${libName}: ${count}
|
|
28
|
+
console.log(` ${chalk.green("●")} ${libName}: ${count} items`);
|
|
21
29
|
if (libConfig) {
|
|
22
30
|
console.log(chalk.gray(` ${libConfig.description}`));
|
|
23
31
|
}
|
|
@@ -34,4 +42,23 @@ export function statsCommand(options) {
|
|
|
34
42
|
console.log();
|
|
35
43
|
console.log(chalk.gray(`To add: pf-docs init --libs ${notIndexed[0].shortName}`));
|
|
36
44
|
}
|
|
45
|
+
// Show examples/episodes status
|
|
46
|
+
const extrasNotIndexed = [];
|
|
47
|
+
if (!areExamplesCloned()) {
|
|
48
|
+
extrasNotIndexed.push("examples");
|
|
49
|
+
}
|
|
50
|
+
if (!areEpisodesCloned()) {
|
|
51
|
+
extrasNotIndexed.push("episodes");
|
|
52
|
+
}
|
|
53
|
+
if (extrasNotIndexed.length > 0) {
|
|
54
|
+
console.log(chalk.bold("Additional sources (not indexed):"));
|
|
55
|
+
if (extrasNotIndexed.includes("examples")) {
|
|
56
|
+
console.log(` ${chalk.gray("○")} examples — ${EXAMPLES_CONFIG.description}`);
|
|
57
|
+
}
|
|
58
|
+
if (extrasNotIndexed.includes("episodes")) {
|
|
59
|
+
console.log(` ${chalk.gray("○")} episodes — ${EPISODES_CONFIG.description}`);
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.gray(`To add: pf-docs init --examples --episodes`));
|
|
63
|
+
}
|
|
37
64
|
}
|