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 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 # All initialized libraries
51
- pf-docs update --libs tca # Specific libraries
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 # All indexed docs
79
- pf-docs list tca --tree # Tree view for one library
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, `pf-docs get <path>` to read an article.
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.1.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")
@@ -4,6 +4,8 @@
4
4
  interface GetOptions {
5
5
  json?: boolean;
6
6
  raw?: boolean;
7
+ preview?: boolean;
8
+ lines?: string;
7
9
  }
8
10
  export declare function getCommand(path: string, options?: GetOptions): void;
9
11
  export {};
@@ -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
- console.log(JSON.stringify(doc, null, 2));
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
  }
@@ -4,6 +4,8 @@
4
4
  interface InitOptions {
5
5
  libs?: string[];
6
6
  all?: boolean;
7
+ examples?: boolean;
8
+ episodes?: boolean;
7
9
  }
8
10
  export declare function initCommand(options: InitOptions): Promise<void>;
9
11
  export {};
@@ -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
- if (librariesToInit.length === 0) {
26
- console.log(chalk.red("No valid libraries specified."));
27
- console.log(`\nUsage: pf-docs init --libs tca dependencies navigation`);
28
- console.log(` pf-docs init --all`);
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
- console.log(chalk.blue(`Libraries to initialize: ${librariesToInit.map((l) => l.shortName).join(", ")}\n`));
33
- // Clone repositories
34
- console.log(chalk.bold("Cloning repositories..."));
35
- for (const lib of librariesToInit) {
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 cloneLibrary(lib);
77
+ await cloneEpisodes();
38
78
  }
39
79
  catch (error) {
40
- console.error(chalk.red(` ✗ Failed to clone ${lib.shortName}:`), error);
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} documents.\n`));
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
  }
@@ -5,6 +5,7 @@ interface ListOptions {
5
5
  tree?: boolean;
6
6
  json?: boolean;
7
7
  available?: boolean;
8
+ source?: string;
8
9
  }
9
10
  export declare function listCommand(lib: string | undefined, options: ListOptions): void;
10
11
  export {};
@@ -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
- console.log(JSON.stringify(libs, null, 2));
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 library and show as tree
58
- const byLibrary = new Map();
84
+ // Group by source and library, show as tree
85
+ const bySource = new Map();
59
86
  for (const doc of docs) {
60
- if (!byLibrary.has(doc.library)) {
61
- byLibrary.set(doc.library, []);
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
- byLibrary.get(doc.library).push(doc);
94
+ sourceMap.get(doc.library).push(doc);
64
95
  }
65
- for (const [library, libraryDocs] of byLibrary) {
66
- console.log(chalk.blue(`${library}/`));
67
- for (let i = 0; i < libraryDocs.length; i++) {
68
- const doc = libraryDocs[i];
69
- const relativePath = doc.path.replace(`${library}/`, "");
70
- const isLast = i === libraryDocs.length - 1;
71
- const prefix = isLast ? "└── " : "├── ";
72
- console.log(chalk.gray(` ${prefix}${relativePath}`));
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
- console.log(chalk.blue(doc.path));
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} documents`));
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
  }
@@ -5,6 +5,7 @@ interface SearchOptions {
5
5
  lib?: string;
6
6
  limit?: string;
7
7
  json?: boolean;
8
+ source?: string;
8
9
  }
9
10
  export declare function searchCommand(query: string, options: SearchOptions): void;
10
11
  export {};
@@ -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
- const results = withIndex(() => searchIndex(query, { lib: options.lib, limit }));
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
- console.log(chalk.bold(`\n🔍 Search results for: "${query}"\n`));
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
- console.log(chalk.blue(`${i + 1}. ${result.title}`));
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
  }
@@ -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 documents: ${stats.totalDocs}`));
16
+ console.log(chalk.blue(`Total indexed items: ${stats.totalDocs}`));
16
17
  console.log();
17
- console.log(chalk.bold("Indexed libraries:"));
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} docs`);
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
  }