coding-friend-cli 1.1.1 → 1.2.1

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.
Files changed (56) hide show
  1. package/README.md +44 -6
  2. package/dist/{chunk-AQXTNLQD.js → chunk-6OI37OZX.js} +9 -1
  3. package/dist/chunk-CSF4FAHL.js +129 -0
  4. package/dist/{chunk-KZT4AFDW.js → chunk-Q4DKU5IG.js} +4 -6
  5. package/dist/dev-MAAWPWML.js +290 -0
  6. package/dist/{host-JBTJCWM2.js → host-2WINWEW7.js} +2 -2
  7. package/dist/index.js +44 -6
  8. package/dist/{init-E6CL3UZQ.js → init-CTCDQKIQ.js} +24 -13
  9. package/dist/{mcp-MWESK6UX.js → mcp-43HCE2KD.js} +2 -2
  10. package/dist/postinstall.js +1 -1
  11. package/dist/{statusline-7D6YU5YM.js → statusline-ARI7I5YM.js} +1 -1
  12. package/dist/{update-IH3G4SN5.js → update-GGCBM7U4.js} +91 -40
  13. package/lib/learn-host/.prettierignore +4 -0
  14. package/lib/learn-host/.prettierrc +8 -0
  15. package/lib/learn-host/CHANGELOG.md +14 -0
  16. package/lib/learn-host/README.md +114 -0
  17. package/lib/learn-host/eslint.config.mjs +6 -0
  18. package/lib/learn-host/next-env.d.ts +1 -1
  19. package/lib/learn-host/next.config.ts +4 -0
  20. package/lib/learn-host/package-lock.json +6039 -391
  21. package/lib/learn-host/package.json +30 -15
  22. package/lib/learn-host/public/logo.svg +1 -0
  23. package/lib/learn-host/src/app/[category]/[slug]/page.tsx +36 -32
  24. package/lib/learn-host/src/app/[category]/page.tsx +2 -3
  25. package/lib/learn-host/src/app/apple-icon.svg +1 -0
  26. package/lib/learn-host/src/app/globals.css +74 -14
  27. package/lib/learn-host/src/app/icon.svg +1 -0
  28. package/lib/learn-host/src/app/layout.tsx +29 -9
  29. package/lib/learn-host/src/app/page.tsx +9 -11
  30. package/lib/learn-host/src/components/Breadcrumbs.tsx +12 -4
  31. package/lib/learn-host/src/components/DocCard.tsx +28 -10
  32. package/lib/learn-host/src/components/MarkdownRenderer.tsx +6 -2
  33. package/lib/learn-host/src/components/MobileNav.tsx +43 -35
  34. package/lib/learn-host/src/components/PagefindSearch.tsx +177 -54
  35. package/lib/learn-host/src/components/Sidebar.tsx +27 -29
  36. package/lib/learn-host/src/components/TableOfContents.tsx +62 -0
  37. package/lib/learn-host/src/components/TagBadge.tsx +1 -1
  38. package/lib/learn-host/src/components/ThemeToggle.tsx +36 -9
  39. package/lib/learn-host/src/components/layout/Footer.tsx +41 -0
  40. package/lib/learn-host/src/components/layout/Header.tsx +117 -0
  41. package/lib/learn-host/src/lib/docs.ts +98 -8
  42. package/lib/learn-host/src/lib/types.ts +7 -1
  43. package/lib/learn-host/tsconfig.json +8 -2
  44. package/lib/learn-host/tsconfig.tsbuildinfo +1 -0
  45. package/lib/learn-mcp/CHANGELOG.md +12 -0
  46. package/lib/learn-mcp/README.md +169 -0
  47. package/lib/learn-mcp/package.json +2 -1
  48. package/lib/learn-mcp/src/index.ts +1 -1
  49. package/lib/learn-mcp/src/lib/docs.ts +1 -3
  50. package/lib/learn-mcp/src/lib/knowledge.ts +2 -1
  51. package/lib/learn-mcp/src/tools/get-review-list.ts +1 -4
  52. package/lib/learn-mcp/src/tools/search-docs.ts +1 -4
  53. package/package.json +14 -6
  54. package/dist/chunk-VHZQ6KEU.js +0 -73
  55. package/lib/learn-host/src/app/search/page.tsx +0 -19
  56. package/lib/learn-host/src/components/SearchBar.tsx +0 -36
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  ensureShellCompletion,
6
6
  hasShellCompletion
7
- } from "./chunk-VHZQ6KEU.js";
7
+ } from "./chunk-CSF4FAHL.js";
8
8
  import {
9
9
  run
10
10
  } from "./chunk-UFGNO6CW.js";
@@ -13,7 +13,7 @@ import {
13
13
  globalConfigPath,
14
14
  localConfigPath,
15
15
  resolvePath
16
- } from "./chunk-AQXTNLQD.js";
16
+ } from "./chunk-6OI37OZX.js";
17
17
  import {
18
18
  log
19
19
  } from "./chunk-6DUFTBTO.js";
@@ -183,7 +183,9 @@ async function setupLearnConfig(gitAvailable = true) {
183
183
  });
184
184
  let categories = DEFAULT_CONFIG.learn.categories;
185
185
  if (catChoice === "custom") {
186
- console.log('Enter categories (format: "name: description"). Empty line to finish.');
186
+ console.log(
187
+ 'Enter categories (format: "name: description"). Empty line to finish.'
188
+ );
187
189
  const customCats = [];
188
190
  let keepGoing = true;
189
191
  while (keepGoing) {
@@ -265,7 +267,11 @@ async function setupClaudePermissions(outputDir, autoCommit) {
265
267
  }
266
268
  permissions.allow = [...existing, ...missing];
267
269
  settings.permissions = permissions;
268
- const { readJson: _r, writeJson: _w, ...restImports } = await import("./json-2XS56OJY.js");
270
+ const {
271
+ readJson: _r,
272
+ writeJson: _w,
273
+ ...restImports
274
+ } = await import("./json-2XS56OJY.js");
269
275
  _w(settingsPath, settings);
270
276
  log.success(`Added ${missing.length} permission rules.`);
271
277
  }
@@ -287,9 +293,7 @@ function isDefaultConfig(config) {
287
293
  }
288
294
  async function saveConfig(config) {
289
295
  if (isDefaultConfig(config)) {
290
- log.dim(
291
- "All settings match defaults \u2014 no config file needed."
292
- );
296
+ log.dim("All settings match defaults \u2014 no config file needed.");
293
297
  return;
294
298
  }
295
299
  const target = await select({
@@ -321,10 +325,20 @@ async function initCommand() {
321
325
  const hasExternalDir = checkLearnConfig() && isExternalOutputDir(resolvedOutputDir);
322
326
  const steps = [
323
327
  { name: "docs", label: "Create docs folders", done: checkDocsFolders() },
324
- ...gitAvailable ? [{ name: "gitignore", label: "Configure .gitignore", done: checkGitignore() }] : [],
328
+ ...gitAvailable ? [
329
+ {
330
+ name: "gitignore",
331
+ label: "Configure .gitignore",
332
+ done: checkGitignore()
333
+ }
334
+ ] : [],
325
335
  { name: "language", label: "Set docs language", done: checkLanguage() },
326
336
  { name: "learn", label: "Configure /cf-learn", done: checkLearnConfig() },
327
- { name: "completion", label: "Setup shell tab completion", done: hasShellCompletion() }
337
+ {
338
+ name: "completion",
339
+ label: "Setup shell tab completion",
340
+ done: hasShellCompletion()
341
+ }
328
342
  ];
329
343
  if (hasExternalDir && resolvedOutputDir) {
330
344
  steps.push({
@@ -405,10 +419,7 @@ async function initCommand() {
405
419
  break;
406
420
  case "permissions":
407
421
  if (resolvedOutputDir) {
408
- await setupClaudePermissions(
409
- resolvedOutputDir,
410
- learnAutoCommit
411
- );
422
+ await setupClaudePermissions(resolvedOutputDir, learnAutoCommit);
412
423
  }
413
424
  break;
414
425
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  getLibPath,
3
3
  resolveDocsDir
4
- } from "./chunk-KZT4AFDW.js";
4
+ } from "./chunk-Q4DKU5IG.js";
5
5
  import "./chunk-HRVSKMNA.js";
6
6
  import {
7
7
  run
8
8
  } from "./chunk-UFGNO6CW.js";
9
- import "./chunk-AQXTNLQD.js";
9
+ import "./chunk-6OI37OZX.js";
10
10
  import {
11
11
  log
12
12
  } from "./chunk-6DUFTBTO.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureShellCompletion
4
- } from "./chunk-VHZQ6KEU.js";
4
+ } from "./chunk-CSF4FAHL.js";
5
5
  import "./chunk-6DUFTBTO.js";
6
6
 
7
7
  // src/postinstall.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  claudeSettingsPath,
3
3
  pluginCachePath
4
- } from "./chunk-AQXTNLQD.js";
4
+ } from "./chunk-6OI37OZX.js";
5
5
  import {
6
6
  log
7
7
  } from "./chunk-6DUFTBTO.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ensureShellCompletion
3
- } from "./chunk-VHZQ6KEU.js";
3
+ } from "./chunk-CSF4FAHL.js";
4
4
  import {
5
5
  commandExists,
6
6
  run,
@@ -10,7 +10,7 @@ import {
10
10
  claudeSettingsPath,
11
11
  installedPluginsPath,
12
12
  pluginCachePath
13
- } from "./chunk-AQXTNLQD.js";
13
+ } from "./chunk-6OI37OZX.js";
14
14
  import {
15
15
  log
16
16
  } from "./chunk-6DUFTBTO.js";
@@ -24,6 +24,15 @@ import { existsSync, readFileSync, readdirSync } from "fs";
24
24
  import { dirname, join } from "path";
25
25
  import { fileURLToPath } from "url";
26
26
  import chalk from "chalk";
27
+ function semverCompare(a, b) {
28
+ const pa = a.split(".").map(Number);
29
+ const pb = b.split(".").map(Number);
30
+ for (let i = 0; i < 3; i++) {
31
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
32
+ if (diff !== 0) return diff > 0 ? 1 : -1;
33
+ }
34
+ return 0;
35
+ }
27
36
  var __dirname = dirname(fileURLToPath(import.meta.url));
28
37
  function getCliVersion() {
29
38
  const pkg = JSON.parse(
@@ -117,10 +126,16 @@ async function updateCommand(opts) {
117
126
  const statuslineVersion = getStatuslineVersion();
118
127
  const cliVersion = getCliVersion();
119
128
  const latestCliVersion = getLatestCliVersion();
120
- log.info(`Plugin version: ${currentVersion ? `v${currentVersion}` : chalk.yellow("not found")}`);
121
- log.info(`Latest plugin version: ${latestVersion ? chalk.green(`v${latestVersion}`) : chalk.yellow("unknown (cannot reach GitHub)")}`);
129
+ log.info(
130
+ `Plugin version: ${currentVersion ? `v${currentVersion}` : chalk.yellow("not found")}`
131
+ );
132
+ log.info(
133
+ `Latest plugin version: ${latestVersion ? chalk.green(`v${latestVersion}`) : chalk.yellow("unknown (cannot reach GitHub)")}`
134
+ );
122
135
  log.info(`CLI version: v${cliVersion}`);
123
- log.info(`Latest CLI version: ${latestCliVersion ? chalk.green(`v${latestCliVersion}`) : chalk.yellow("unknown (cannot reach npm)")}`);
136
+ log.info(
137
+ `Latest CLI version: ${latestCliVersion ? chalk.green(`v${latestCliVersion}`) : chalk.yellow("unknown (cannot reach npm)")}`
138
+ );
124
139
  log.info(
125
140
  `Statusline version: ${statuslineVersion ? chalk.green(`v${statuslineVersion}`) : chalk.yellow("not configured")}`
126
141
  );
@@ -130,37 +145,54 @@ async function updateCommand(opts) {
130
145
  log.warn(
131
146
  "Cannot check latest plugin version. Verify manually at https://github.com/dinhanhthi/coding-friend/releases"
132
147
  );
133
- } else if (currentVersion === latestVersion) {
134
- log.success(`Plugin already on the latest version (${chalk.green(`v${latestVersion}`)}).`);
148
+ } else if (!currentVersion) {
149
+ log.warn(
150
+ "Plugin not installed. Run: claude plugin install coding-friend@coding-friend-marketplace"
151
+ );
135
152
  } else {
136
- log.step(`Plugin update available: ${chalk.yellow(`v${currentVersion}`)} \u2192 ${chalk.green(`v${latestVersion}`)}`);
137
- if (!commandExists("claude")) {
138
- log.error(
139
- "Claude CLI not found. Install it first, or run: claude plugin update coding-friend@coding-friend-marketplace"
153
+ const cmp = semverCompare(currentVersion, latestVersion);
154
+ if (cmp === 0) {
155
+ log.success(
156
+ `Plugin already on the latest version (${chalk.green(`v${latestVersion}`)}).`
157
+ );
158
+ } else if (cmp > 0) {
159
+ log.info(
160
+ `Plugin is ahead of latest release (local: ${chalk.cyan(`v${currentVersion}`)}, latest: v${latestVersion}). Skipping.`
140
161
  );
141
162
  } else {
142
- log.step("Updating plugin...");
143
- const result = run("claude", [
144
- "plugin",
145
- "update",
146
- "coding-friend@coding-friend-marketplace"
147
- ]);
148
- if (result === null) {
149
- log.error("Plugin update failed. Try manually: claude plugin update coding-friend@coding-friend-marketplace");
163
+ log.step(
164
+ `Plugin update available: ${chalk.yellow(`v${currentVersion}`)} \u2192 ${chalk.green(`v${latestVersion}`)}`
165
+ );
166
+ if (!commandExists("claude")) {
167
+ log.error(
168
+ "Claude CLI not found. Install it first, or run: claude plugin update coding-friend@coding-friend-marketplace"
169
+ );
150
170
  } else {
151
- log.success("Plugin updated!");
152
- let newVersion = currentVersion;
153
- for (let i = 0; i < 5; i++) {
154
- newVersion = getInstalledVersion();
155
- if (newVersion !== currentVersion) break;
156
- if (i < 4) sleepSync(1e3);
157
- }
158
- if (newVersion !== currentVersion) {
159
- log.success(`Plugin updated to ${chalk.green(`v${newVersion}`)}`);
160
- } else {
161
- log.warn(
162
- "Version in installed_plugins.json unchanged. Cache may still have been updated."
171
+ log.step("Updating plugin...");
172
+ const result = run("claude", [
173
+ "plugin",
174
+ "update",
175
+ "coding-friend@coding-friend-marketplace"
176
+ ]);
177
+ if (result === null) {
178
+ log.error(
179
+ "Plugin update failed. Try manually: claude plugin update coding-friend@coding-friend-marketplace"
163
180
  );
181
+ } else {
182
+ log.success("Plugin updated!");
183
+ let newVersion = currentVersion;
184
+ for (let i = 0; i < 5; i++) {
185
+ newVersion = getInstalledVersion();
186
+ if (newVersion !== currentVersion) break;
187
+ if (i < 4) sleepSync(1e3);
188
+ }
189
+ if (newVersion !== currentVersion) {
190
+ log.success(`Plugin updated to ${chalk.green(`v${newVersion}`)}`);
191
+ } else {
192
+ log.warn(
193
+ "Version in installed_plugins.json unchanged. Cache may still have been updated."
194
+ );
195
+ }
164
196
  }
165
197
  }
166
198
  }
@@ -169,16 +201,33 @@ async function updateCommand(opts) {
169
201
  if (doCli) {
170
202
  if (!latestCliVersion) {
171
203
  log.warn("Cannot check latest CLI version from npm.");
172
- } else if (cliVersion === latestCliVersion) {
173
- log.success(`CLI already on the latest version (${chalk.green(`v${latestCliVersion}`)}).`);
174
204
  } else {
175
- log.step(`CLI update available: ${chalk.yellow(`v${cliVersion}`)} \u2192 ${chalk.green(`v${latestCliVersion}`)}`);
176
- log.step("Updating CLI...");
177
- const result = run("npm", ["install", "-g", "coding-friend-cli@latest"]);
178
- if (result === null) {
179
- log.error("CLI update failed. Try manually: npm install -g coding-friend-cli@latest");
205
+ const cmp = semverCompare(cliVersion, latestCliVersion);
206
+ if (cmp === 0) {
207
+ log.success(
208
+ `CLI already on the latest version (${chalk.green(`v${latestCliVersion}`)}).`
209
+ );
210
+ } else if (cmp > 0) {
211
+ log.info(
212
+ `CLI is ahead of latest release (local: ${chalk.cyan(`v${cliVersion}`)}, latest: v${latestCliVersion}). Skipping.`
213
+ );
180
214
  } else {
181
- log.success(`CLI updated to ${chalk.green(`v${latestCliVersion}`)}`);
215
+ log.step(
216
+ `CLI update available: ${chalk.yellow(`v${cliVersion}`)} \u2192 ${chalk.green(`v${latestCliVersion}`)}`
217
+ );
218
+ log.step("Updating CLI...");
219
+ const result = run("npm", [
220
+ "install",
221
+ "-g",
222
+ "coding-friend-cli@latest"
223
+ ]);
224
+ if (result === null) {
225
+ log.error(
226
+ "CLI update failed. Try manually: npm install -g coding-friend-cli@latest"
227
+ );
228
+ } else {
229
+ log.success(`CLI updated to ${chalk.green(`v${latestCliVersion}`)}`);
230
+ }
182
231
  }
183
232
  }
184
233
  }
@@ -187,7 +236,9 @@ async function updateCommand(opts) {
187
236
  if (targetVersion) {
188
237
  log.step("Updating statusline...");
189
238
  if (updateStatusline(targetVersion)) {
190
- log.success(`Statusline updated to ${chalk.green(`v${targetVersion}`)}`);
239
+ log.success(
240
+ `Statusline updated to ${chalk.green(`v${targetVersion}`)}`
241
+ );
191
242
  }
192
243
  } else {
193
244
  log.warn("No cached plugin version found for statusline update.");
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ .next
3
+ out
4
+ public/_pagefind
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "tabWidth": 2,
5
+ "trailingComma": "all",
6
+ "printWidth": 80,
7
+ "plugins": ["prettier-plugin-tailwindcss"]
8
+ }
@@ -0,0 +1,14 @@
1
+ # Changelog (Learn Host)
2
+
3
+ ## v0.0.2 (2026-03-01)
4
+
5
+ - Update header styling for consistency with ecosystem redesign
6
+ - Code formatting and style improvements
7
+
8
+ ## v0.0.1
9
+
10
+ - Next.js app for hosting learning docs at `localhost:3333`
11
+ - ISR (Incremental Static Regeneration) for auto-updating docs without rebuild
12
+ - Full-text search via Pagefind
13
+ - Modern UI with command palette, dark theme, and responsive layout
14
+ - Markdown rendering with syntax highlighting and GFM support
@@ -0,0 +1,114 @@
1
+ # coding-friend-learn-host
2
+
3
+ Next.js app that renders your `/cf-learn` docs as a browsable website with search, categories, and dark mode.
4
+
5
+ ## Usage (via CLI)
6
+
7
+ ```bash
8
+ cf host # serves docs/learn/ on port 3333
9
+ cf host ./my-docs # serves a custom directory
10
+ cf host -p 4000 # custom port
11
+ ```
12
+
13
+ The CLI handles deps install, build, and serving automatically.
14
+
15
+ ## Local Development
16
+
17
+ Run the app directly without the CLI — useful when working on the UI itself.
18
+
19
+ ### 1. Install dependencies
20
+
21
+ ```bash
22
+ cd cli/lib/learn-host
23
+ npm install
24
+ ```
25
+
26
+ ### 2. Point to a docs directory
27
+
28
+ The app resolves docs via (in order):
29
+
30
+ 1. `DOCS_DIR` env var
31
+ 2. Local `.coding-friend/config.json` → `learn.outputDir`
32
+ 3. Global `~/.coding-friend/config.json` → `learn.outputDir`
33
+ 4. Default: `docs/learn/` relative to project root
34
+
35
+ For local dev, set `DOCS_DIR` to any directory with the expected structure:
36
+
37
+ ```
38
+ docs/
39
+ └── learn/
40
+ ├── category-one/
41
+ │ ├── my-doc.md
42
+ │ └── another-doc.md
43
+ └── category-two/
44
+ └── some-doc.md
45
+ ```
46
+
47
+ Each `.md` file should have frontmatter:
48
+
49
+ ```md
50
+ ---
51
+ title: My Doc Title
52
+ category: category-one
53
+ tags: [typescript, patterns]
54
+ created: 2025-01-01
55
+ updated: 2025-01-15
56
+ ---
57
+
58
+ Content here...
59
+ ```
60
+
61
+ ### 3. Run dev server
62
+
63
+ ```bash
64
+ # Point to this repo's own learn docs (if they exist)
65
+ DOCS_DIR=../../../docs/learn npm run dev
66
+
67
+ # Or point to any other project's docs
68
+ DOCS_DIR=/path/to/your/project/docs/learn npm run dev
69
+ ```
70
+
71
+ App runs at `http://localhost:3333`.
72
+
73
+ > **Note:** `npm run dev` does not rebuild the Pagefind search index. If you've previously run `npm run build`, search will still work but uses the old index — new or edited docs won't appear in search results until you build again.
74
+
75
+ ### 4. Full build (with search)
76
+
77
+ ```bash
78
+ DOCS_DIR=/path/to/docs npm run build
79
+ npx next start -p 3333
80
+ ```
81
+
82
+ `postbuild` runs `pagefind` to index docs for full-text search.
83
+
84
+ ## Structure
85
+
86
+ ```
87
+ src/
88
+ ├── app/
89
+ │ ├── page.tsx # Homepage: recent docs, categories, tags
90
+ │ ├── [category]/page.tsx # Category listing
91
+ │ ├── [category]/[slug]/page.tsx # Individual doc
92
+ │ └── layout.tsx
93
+ ├── components/
94
+ │ ├── MarkdownRenderer.tsx # Renders .md with syntax highlighting
95
+ │ ├── TableOfContents.tsx # Auto-generated from headings
96
+ │ ├── Sidebar.tsx # Category navigation
97
+ │ ├── PagefindSearch.tsx # Full-text search (build-time index)
98
+ │ └── ThemeToggle.tsx
99
+ └── lib/
100
+ ├── docs.ts # getAllDocs, getDocBySlug, etc.
101
+ └── types.ts
102
+ ```
103
+
104
+ ## How It Fits Together
105
+
106
+ ```
107
+ cf host [path]
108
+ └─ resolves docs dir
109
+ └─ npm install (one-time)
110
+ └─ npm run build (with DOCS_DIR env)
111
+ └─ npx next start -p 3333 (with DOCS_DIR env)
112
+ ```
113
+
114
+ ISR (Incremental Static Regeneration) is enabled, so new or edited docs appear on the next page refresh without a rebuild.
@@ -0,0 +1,6 @@
1
+ import nextConfig from "eslint-config-next";
2
+ import eslintConfigPrettier from "eslint-config-prettier";
3
+
4
+ const eslintConfig = [...nextConfig, eslintConfigPrettier];
5
+
6
+ export default eslintConfig;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- /// <reference path="./.next/types/routes.d.ts" />
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -3,6 +3,10 @@ import type { NextConfig } from "next";
3
3
  const nextConfig: NextConfig = {
4
4
  trailingSlash: true,
5
5
  images: { unoptimized: true },
6
+ reactCompiler: true,
7
+ turbopack: {
8
+ root: __dirname,
9
+ },
6
10
  };
7
11
 
8
12
  export default nextConfig;