coding-friend-cli 1.2.0 → 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.
- package/README.md +29 -6
- package/dist/{chunk-R6ZYK4UX.js → chunk-CSF4FAHL.js} +2 -1
- package/dist/{chunk-5HZJX47M.js → chunk-Q4DKU5IG.js} +3 -5
- package/dist/{dev-LZASFXZZ.js → dev-MAAWPWML.js} +65 -18
- package/dist/{host-BK6DYFWF.js → host-2WINWEW7.js} +1 -1
- package/dist/index.js +30 -13
- package/dist/{init-2UKYE2KV.js → init-CTCDQKIQ.js} +23 -12
- package/dist/{mcp-CH4SKZSX.js → mcp-43HCE2KD.js} +1 -1
- package/dist/postinstall.js +1 -1
- package/dist/{update-5A2OP6EY.js → update-GGCBM7U4.js} +45 -15
- package/lib/learn-host/.prettierignore +1 -0
- package/lib/learn-host/CHANGELOG.md +5 -0
- package/lib/learn-host/README.md +114 -0
- package/lib/learn-host/next-env.d.ts +1 -1
- package/lib/learn-host/next.config.ts +3 -0
- package/lib/learn-host/package.json +1 -1
- package/lib/learn-host/public/logo.svg +1 -1
- package/lib/learn-host/src/app/apple-icon.svg +1 -1
- package/lib/learn-host/src/app/icon.svg +1 -1
- package/lib/learn-host/src/components/Sidebar.tsx +1 -1
- package/lib/learn-host/src/lib/docs.ts +2 -2
- package/lib/learn-mcp/CHANGELOG.md +5 -0
- package/lib/learn-mcp/README.md +169 -0
- package/lib/learn-mcp/package.json +2 -1
- package/lib/learn-mcp/src/index.ts +1 -1
- package/lib/learn-mcp/src/lib/docs.ts +1 -3
- package/lib/learn-mcp/src/lib/knowledge.ts +2 -1
- package/lib/learn-mcp/src/tools/get-review-list.ts +1 -4
- package/lib/learn-mcp/src/tools/search-docs.ts +1 -4
- package/package.json +2 -2
- package/lib/learn-host/public/_pagefind/fragment/en_1172b3c.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_118ad1c.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_32ab3d8.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_441f1e1.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_4452de4.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_4ae396d.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_58ee89d.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_6dd2225.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_765a297.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_7a4cc4a.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_8050261.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_83eaedf.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_925bc5f.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_95f3dd5.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_96d7a02.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_971f951.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_a446c32.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_a5ee367.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_b11c248.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_b13c52e.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_b5bd69b.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_b625d7d.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_bf63915.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_c52b25b.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_c9db556.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_d1537ee.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_d2e6412.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_d2f47a4.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_d361292.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_d727ec8.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_e11cd8f.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_e481f19.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_eee2805.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/fragment/en_f4de6c4.pf_fragment +0 -0
- package/lib/learn-host/public/_pagefind/index/en_1ecb9d5.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_37e362b.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_538eee7.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_5751dc8.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_67f794d.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_7458f81.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/index/en_e21f7e1.pf_index +0 -0
- package/lib/learn-host/public/_pagefind/pagefind-entry.json +0 -1
- package/lib/learn-host/public/_pagefind/pagefind-highlight.js +0 -1064
- package/lib/learn-host/public/_pagefind/pagefind-modular-ui.css +0 -214
- package/lib/learn-host/public/_pagefind/pagefind-modular-ui.js +0 -8
- package/lib/learn-host/public/_pagefind/pagefind-ui.css +0 -1
- package/lib/learn-host/public/_pagefind/pagefind-ui.js +0 -2
- package/lib/learn-host/public/_pagefind/pagefind.en_104569cceb.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_1075df6f16.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_139f35f6e5.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_46bfc9f7e1.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_76b8937bbc.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_83cbfb0fd5.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.en_b1d168d536.pf_meta +0 -0
- package/lib/learn-host/public/_pagefind/pagefind.js +0 -6
- package/lib/learn-host/public/_pagefind/wasm.en.pagefind +0 -0
- package/lib/learn-host/public/_pagefind/wasm.unknown.pagefind +0 -0
package/README.md
CHANGED
|
@@ -75,17 +75,40 @@ npm ls -g coding-friend-cli
|
|
|
75
75
|
#└── coding-friend-cli@1.1.1 -> ./../../../../../git/coding-friend/cli
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Running tests
|
|
79
|
+
|
|
80
|
+
Tests are written with [Vitest](https://vitest.dev/) and live in `src/lib/__tests__/`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cd cli
|
|
84
|
+
|
|
85
|
+
# Run all tests once
|
|
86
|
+
npm test
|
|
87
|
+
|
|
88
|
+
# Watch mode (re-runs on file changes)
|
|
89
|
+
npm run test:watch
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Current coverage: `lib/json.ts`, `lib/paths.ts`, `lib/exec.ts`.
|
|
93
|
+
|
|
78
94
|
## Publish CLI to npm
|
|
79
95
|
|
|
96
|
+
Publishing is automated via GitHub Actions (`.github/workflows/publish-cli.yml`). Push a tag with the `cli-v*` prefix to trigger it:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Bump version in cli/package.json first, then tag and push
|
|
100
|
+
git tag cli-v1.2.3
|
|
101
|
+
git push origin cli-v1.2.3
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The workflow will build, bundle, and publish to npm automatically (with provenance), then create a GitHub Release with the changelog for that version.
|
|
105
|
+
|
|
106
|
+
**Manual publish (if needed):**
|
|
107
|
+
|
|
80
108
|
```bash
|
|
81
|
-
# From the root of coding-friend project
|
|
82
109
|
cd cli
|
|
83
110
|
npm login # Login if not already
|
|
84
111
|
npm publish # Build + bundle + publish
|
|
85
|
-
|
|
86
|
-
# To bump a version
|
|
87
|
-
npm version patch # 1.0.1 -> 1.0.2
|
|
88
|
-
npm version minor # 1.0.1 -> 1.1.0
|
|
89
112
|
```
|
|
90
113
|
|
|
91
114
|
`prepublishOnly` runs automatically: builds TypeScript → `dist/` and bundles libs from `lib/`.
|
|
@@ -96,4 +119,4 @@ npm version minor # 1.0.1 -> 1.1.0
|
|
|
96
119
|
|
|
97
120
|
## License
|
|
98
121
|
|
|
99
|
-
MIT
|
|
122
|
+
MIT
|
|
@@ -103,7 +103,8 @@ function ensureShellCompletion(opts) {
|
|
|
103
103
|
const existing = extractExistingBlock(content);
|
|
104
104
|
const expectedBlock = newBlock.trim();
|
|
105
105
|
if (existing && existing.trim() === expectedBlock) {
|
|
106
|
-
if (!opts?.silent)
|
|
106
|
+
if (!opts?.silent)
|
|
107
|
+
log.dim(`Tab completion already up-to-date in ~/${rcName}`);
|
|
107
108
|
return false;
|
|
108
109
|
}
|
|
109
110
|
const updated = replaceBlock(content, newBlock);
|
|
@@ -29,12 +29,10 @@ import { dirname, join } from "path";
|
|
|
29
29
|
import { fileURLToPath } from "url";
|
|
30
30
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
31
|
function getLibPath(name) {
|
|
32
|
-
const
|
|
33
|
-
if (existsSync(
|
|
34
|
-
const sibling = join(__dirname, "..", "..", "..", "lib", name);
|
|
35
|
-
if (existsSync(sibling)) return sibling;
|
|
32
|
+
const libDir = join(__dirname, "..", "..", "lib", name);
|
|
33
|
+
if (existsSync(libDir)) return libDir;
|
|
36
34
|
throw new Error(
|
|
37
|
-
`Could not find lib/${name}. Ensure it exists in the CLI package
|
|
35
|
+
`Could not find lib/${name}. Ensure it exists in the CLI package.`
|
|
38
36
|
);
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -17,7 +17,14 @@ import {
|
|
|
17
17
|
} from "./chunk-IUTXHCP7.js";
|
|
18
18
|
|
|
19
19
|
// src/commands/dev.ts
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
unlinkSync,
|
|
23
|
+
readdirSync,
|
|
24
|
+
statSync,
|
|
25
|
+
mkdirSync,
|
|
26
|
+
copyFileSync
|
|
27
|
+
} from "fs";
|
|
21
28
|
import { resolve, join } from "path";
|
|
22
29
|
import chalk from "chalk";
|
|
23
30
|
var REMOTE_URL = "https://github.com/dinhanhthi/coding-friend.git";
|
|
@@ -40,7 +47,9 @@ function isMarketplaceRegistered() {
|
|
|
40
47
|
}
|
|
41
48
|
function ensureClaude() {
|
|
42
49
|
if (!commandExists("claude")) {
|
|
43
|
-
log.error(
|
|
50
|
+
log.error(
|
|
51
|
+
"Claude CLI not found. Install it first: https://docs.anthropic.com/en/docs/claude-code"
|
|
52
|
+
);
|
|
44
53
|
return false;
|
|
45
54
|
}
|
|
46
55
|
return true;
|
|
@@ -73,18 +82,30 @@ async function devOnCommand(path) {
|
|
|
73
82
|
`);
|
|
74
83
|
log.info(`Local path: ${chalk.cyan(localPath)}`);
|
|
75
84
|
if (isPluginInstalled()) {
|
|
76
|
-
if (!runClaude(
|
|
85
|
+
if (!runClaude(
|
|
86
|
+
["plugin", "uninstall", PLUGIN_ID],
|
|
87
|
+
"Uninstalling remote plugin..."
|
|
88
|
+
)) {
|
|
77
89
|
run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
if (isMarketplaceRegistered()) {
|
|
81
|
-
runClaude(
|
|
93
|
+
runClaude(
|
|
94
|
+
["plugin", "marketplace", "remove", MARKETPLACE_NAME],
|
|
95
|
+
"Removing remote marketplace..."
|
|
96
|
+
);
|
|
82
97
|
}
|
|
83
|
-
if (!runClaude(
|
|
98
|
+
if (!runClaude(
|
|
99
|
+
["plugin", "marketplace", "add", localPath],
|
|
100
|
+
"Adding local marketplace..."
|
|
101
|
+
)) {
|
|
84
102
|
log.error("Failed to add local marketplace. Aborting.");
|
|
85
103
|
return;
|
|
86
104
|
}
|
|
87
|
-
if (!runClaude(
|
|
105
|
+
if (!runClaude(
|
|
106
|
+
["plugin", "install", PLUGIN_ID],
|
|
107
|
+
"Installing plugin from local source..."
|
|
108
|
+
)) {
|
|
88
109
|
if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
|
|
89
110
|
log.error("Failed to install local plugin.");
|
|
90
111
|
return;
|
|
@@ -96,7 +117,9 @@ async function devOnCommand(path) {
|
|
|
96
117
|
};
|
|
97
118
|
writeJson(devStatePath(), devState);
|
|
98
119
|
console.log();
|
|
99
|
-
log.success(
|
|
120
|
+
log.success(
|
|
121
|
+
`Dev mode ${chalk.green("ON")} \u2014 using local plugin from ${chalk.cyan(localPath)}`
|
|
122
|
+
);
|
|
100
123
|
log.dim("Restart Claude Code to see changes.");
|
|
101
124
|
}
|
|
102
125
|
async function devOffCommand() {
|
|
@@ -106,22 +129,36 @@ async function devOffCommand() {
|
|
|
106
129
|
return;
|
|
107
130
|
}
|
|
108
131
|
if (!ensureClaude()) return;
|
|
109
|
-
console.log(
|
|
132
|
+
console.log(
|
|
133
|
+
`
|
|
110
134
|
=== ${chalk.yellow("Switching back to remote marketplace")} ===
|
|
111
|
-
`
|
|
135
|
+
`
|
|
136
|
+
);
|
|
112
137
|
if (isPluginInstalled()) {
|
|
113
|
-
if (!runClaude(
|
|
138
|
+
if (!runClaude(
|
|
139
|
+
["plugin", "uninstall", PLUGIN_ID],
|
|
140
|
+
"Uninstalling local plugin..."
|
|
141
|
+
)) {
|
|
114
142
|
run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
|
|
115
143
|
}
|
|
116
144
|
}
|
|
117
145
|
if (isMarketplaceRegistered()) {
|
|
118
|
-
runClaude(
|
|
146
|
+
runClaude(
|
|
147
|
+
["plugin", "marketplace", "remove", MARKETPLACE_NAME],
|
|
148
|
+
"Removing local marketplace..."
|
|
149
|
+
);
|
|
119
150
|
}
|
|
120
|
-
if (!runClaude(
|
|
151
|
+
if (!runClaude(
|
|
152
|
+
["plugin", "marketplace", "add", REMOTE_URL],
|
|
153
|
+
"Adding remote marketplace..."
|
|
154
|
+
)) {
|
|
121
155
|
log.error("Failed to add remote marketplace.");
|
|
122
156
|
return;
|
|
123
157
|
}
|
|
124
|
-
if (!runClaude(
|
|
158
|
+
if (!runClaude(
|
|
159
|
+
["plugin", "install", PLUGIN_ID],
|
|
160
|
+
"Installing plugin from remote..."
|
|
161
|
+
)) {
|
|
125
162
|
if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
|
|
126
163
|
log.error("Failed to install remote plugin.");
|
|
127
164
|
return;
|
|
@@ -136,7 +173,9 @@ async function devOffCommand() {
|
|
|
136
173
|
log.dim("Restart Claude Code to see changes.");
|
|
137
174
|
}
|
|
138
175
|
function getMarketplaceSource() {
|
|
139
|
-
const data = readJson(
|
|
176
|
+
const data = readJson(
|
|
177
|
+
knownMarketplacesPath()
|
|
178
|
+
);
|
|
140
179
|
if (!data || !(MARKETPLACE_NAME in data)) return null;
|
|
141
180
|
const entry = data[MARKETPLACE_NAME];
|
|
142
181
|
const src = entry.source;
|
|
@@ -171,7 +210,9 @@ async function devSyncCommand() {
|
|
|
171
210
|
const localPath = state.localPath;
|
|
172
211
|
const pluginSrcDir = join(localPath, "plugin");
|
|
173
212
|
if (!existsSync(pluginSrcDir)) {
|
|
174
|
-
log.error(
|
|
213
|
+
log.error(
|
|
214
|
+
`No plugin/ directory found at ${localPath}. Make sure you point to the coding-friend repo root.`
|
|
215
|
+
);
|
|
175
216
|
return;
|
|
176
217
|
}
|
|
177
218
|
const cacheBase = pluginCachePath();
|
|
@@ -190,14 +231,18 @@ async function devSyncCommand() {
|
|
|
190
231
|
}
|
|
191
232
|
}
|
|
192
233
|
if (!cacheVersionDir) {
|
|
193
|
-
log.error(
|
|
234
|
+
log.error(
|
|
235
|
+
"No cached plugin version found. Run `cf dev off && cf dev on` first."
|
|
236
|
+
);
|
|
194
237
|
return;
|
|
195
238
|
}
|
|
196
239
|
const shortDest = cacheVersionDir.replace(process.env.HOME ?? "", "~");
|
|
197
240
|
log.step(`Syncing ${chalk.cyan(pluginSrcDir)} \u2192 ${chalk.dim(shortDest)}`);
|
|
198
241
|
const fileCount = { n: 0 };
|
|
199
242
|
copyDirRecursive(pluginSrcDir, cacheVersionDir, fileCount);
|
|
200
|
-
log.success(
|
|
243
|
+
log.success(
|
|
244
|
+
`Synced ${chalk.green(fileCount.n)} files. Restart Claude Code to apply changes.`
|
|
245
|
+
);
|
|
201
246
|
}
|
|
202
247
|
async function devRestartCommand(path) {
|
|
203
248
|
const state = getDevState();
|
|
@@ -232,7 +277,9 @@ async function devStatusCommand() {
|
|
|
232
277
|
} else {
|
|
233
278
|
log.warn(`Marketplace "${MARKETPLACE_NAME}" not registered.`);
|
|
234
279
|
}
|
|
235
|
-
log.info(
|
|
280
|
+
log.info(
|
|
281
|
+
`Plugin installed: ${installed ? chalk.green("yes") : chalk.yellow("no")}`
|
|
282
|
+
);
|
|
236
283
|
}
|
|
237
284
|
export {
|
|
238
285
|
devOffCommand,
|
package/dist/index.js
CHANGED
|
@@ -10,17 +10,19 @@ var pkg = JSON.parse(
|
|
|
10
10
|
readFileSync(join(__dirname, "..", "package.json"), "utf-8")
|
|
11
11
|
);
|
|
12
12
|
var program = new Command();
|
|
13
|
-
program.name("cf").description(
|
|
13
|
+
program.name("cf").description(
|
|
14
|
+
"coding-friend CLI \u2014 host learning docs, setup MCP, init projects"
|
|
15
|
+
).version(pkg.version, "-v, --version");
|
|
14
16
|
program.command("init").description("Initialize coding-friend in current project").action(async () => {
|
|
15
|
-
const { initCommand } = await import("./init-
|
|
17
|
+
const { initCommand } = await import("./init-CTCDQKIQ.js");
|
|
16
18
|
await initCommand();
|
|
17
19
|
});
|
|
18
20
|
program.command("host").description("Build and serve learning docs as a static website").argument("[path]", "path to docs folder").option("-p, --port <port>", "port number", "3333").action(async (path, opts) => {
|
|
19
|
-
const { hostCommand } = await import("./host-
|
|
21
|
+
const { hostCommand } = await import("./host-2WINWEW7.js");
|
|
20
22
|
await hostCommand(path, opts);
|
|
21
23
|
});
|
|
22
24
|
program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
|
|
23
|
-
const { mcpCommand } = await import("./mcp-
|
|
25
|
+
const { mcpCommand } = await import("./mcp-43HCE2KD.js");
|
|
24
26
|
await mcpCommand(path);
|
|
25
27
|
});
|
|
26
28
|
program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
|
|
@@ -28,28 +30,43 @@ program.command("statusline").description("Setup coding-friend statusline in Cla
|
|
|
28
30
|
await statuslineCommand();
|
|
29
31
|
});
|
|
30
32
|
program.command("update").description("Update coding-friend plugin, CLI, and statusline").option("--cli", "Update only the CLI (npm package)").option("--plugin", "Update only the Claude Code plugin").option("--statusline", "Update only the statusline").action(async (opts) => {
|
|
31
|
-
const { updateCommand } = await import("./update-
|
|
33
|
+
const { updateCommand } = await import("./update-GGCBM7U4.js");
|
|
32
34
|
await updateCommand(opts);
|
|
33
35
|
});
|
|
34
|
-
var dev = program.command("dev").description("
|
|
36
|
+
var dev = program.command("dev").description("Development mode commands");
|
|
37
|
+
program.addHelpText(
|
|
38
|
+
"after",
|
|
39
|
+
`
|
|
40
|
+
Dev subcommands:
|
|
41
|
+
dev on [path] Switch to local plugin source
|
|
42
|
+
dev off Switch back to remote marketplace
|
|
43
|
+
dev status Show current dev mode
|
|
44
|
+
dev sync Copy local source to plugin cache
|
|
45
|
+
dev restart [path] Reinstall local dev plugin (off + on)`
|
|
46
|
+
);
|
|
35
47
|
dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
|
|
36
|
-
const { devOnCommand } = await import("./dev-
|
|
48
|
+
const { devOnCommand } = await import("./dev-MAAWPWML.js");
|
|
37
49
|
await devOnCommand(path);
|
|
38
50
|
});
|
|
39
51
|
dev.command("off").description("Switch back to remote marketplace").action(async () => {
|
|
40
|
-
const { devOffCommand } = await import("./dev-
|
|
52
|
+
const { devOffCommand } = await import("./dev-MAAWPWML.js");
|
|
41
53
|
await devOffCommand();
|
|
42
54
|
});
|
|
43
55
|
dev.command("status").description("Show current dev mode").action(async () => {
|
|
44
|
-
const { devStatusCommand } = await import("./dev-
|
|
56
|
+
const { devStatusCommand } = await import("./dev-MAAWPWML.js");
|
|
45
57
|
await devStatusCommand();
|
|
46
58
|
});
|
|
47
|
-
dev.command("sync").description(
|
|
48
|
-
|
|
59
|
+
dev.command("sync").description(
|
|
60
|
+
"Copy local source files to plugin cache (no version bump needed)"
|
|
61
|
+
).action(async () => {
|
|
62
|
+
const { devSyncCommand } = await import("./dev-MAAWPWML.js");
|
|
49
63
|
await devSyncCommand();
|
|
50
64
|
});
|
|
51
|
-
dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
|
|
52
|
-
|
|
65
|
+
dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
|
|
66
|
+
"[path]",
|
|
67
|
+
"path to local coding-friend repo (default: saved path or cwd)"
|
|
68
|
+
).action(async (path) => {
|
|
69
|
+
const { devRestartCommand } = await import("./dev-MAAWPWML.js");
|
|
53
70
|
await devRestartCommand(path);
|
|
54
71
|
});
|
|
55
72
|
program.parse();
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
ensureShellCompletion,
|
|
6
6
|
hasShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CSF4FAHL.js";
|
|
8
8
|
import {
|
|
9
9
|
run
|
|
10
10
|
} from "./chunk-UFGNO6CW.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(
|
|
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 {
|
|
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 ? [
|
|
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
|
-
{
|
|
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
|
}
|
package/dist/postinstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureShellCompletion
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CSF4FAHL.js";
|
|
4
4
|
import {
|
|
5
5
|
commandExists,
|
|
6
6
|
run,
|
|
@@ -126,10 +126,16 @@ async function updateCommand(opts) {
|
|
|
126
126
|
const statuslineVersion = getStatuslineVersion();
|
|
127
127
|
const cliVersion = getCliVersion();
|
|
128
128
|
const latestCliVersion = getLatestCliVersion();
|
|
129
|
-
log.info(
|
|
130
|
-
|
|
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
|
+
);
|
|
131
135
|
log.info(`CLI version: v${cliVersion}`);
|
|
132
|
-
log.info(
|
|
136
|
+
log.info(
|
|
137
|
+
`Latest CLI version: ${latestCliVersion ? chalk.green(`v${latestCliVersion}`) : chalk.yellow("unknown (cannot reach npm)")}`
|
|
138
|
+
);
|
|
133
139
|
log.info(
|
|
134
140
|
`Statusline version: ${statuslineVersion ? chalk.green(`v${statuslineVersion}`) : chalk.yellow("not configured")}`
|
|
135
141
|
);
|
|
@@ -140,15 +146,23 @@ async function updateCommand(opts) {
|
|
|
140
146
|
"Cannot check latest plugin version. Verify manually at https://github.com/dinhanhthi/coding-friend/releases"
|
|
141
147
|
);
|
|
142
148
|
} else if (!currentVersion) {
|
|
143
|
-
log.warn(
|
|
149
|
+
log.warn(
|
|
150
|
+
"Plugin not installed. Run: claude plugin install coding-friend@coding-friend-marketplace"
|
|
151
|
+
);
|
|
144
152
|
} else {
|
|
145
153
|
const cmp = semverCompare(currentVersion, latestVersion);
|
|
146
154
|
if (cmp === 0) {
|
|
147
|
-
log.success(
|
|
155
|
+
log.success(
|
|
156
|
+
`Plugin already on the latest version (${chalk.green(`v${latestVersion}`)}).`
|
|
157
|
+
);
|
|
148
158
|
} else if (cmp > 0) {
|
|
149
|
-
log.info(
|
|
159
|
+
log.info(
|
|
160
|
+
`Plugin is ahead of latest release (local: ${chalk.cyan(`v${currentVersion}`)}, latest: v${latestVersion}). Skipping.`
|
|
161
|
+
);
|
|
150
162
|
} else {
|
|
151
|
-
log.step(
|
|
163
|
+
log.step(
|
|
164
|
+
`Plugin update available: ${chalk.yellow(`v${currentVersion}`)} \u2192 ${chalk.green(`v${latestVersion}`)}`
|
|
165
|
+
);
|
|
152
166
|
if (!commandExists("claude")) {
|
|
153
167
|
log.error(
|
|
154
168
|
"Claude CLI not found. Install it first, or run: claude plugin update coding-friend@coding-friend-marketplace"
|
|
@@ -161,7 +175,9 @@ async function updateCommand(opts) {
|
|
|
161
175
|
"coding-friend@coding-friend-marketplace"
|
|
162
176
|
]);
|
|
163
177
|
if (result === null) {
|
|
164
|
-
log.error(
|
|
178
|
+
log.error(
|
|
179
|
+
"Plugin update failed. Try manually: claude plugin update coding-friend@coding-friend-marketplace"
|
|
180
|
+
);
|
|
165
181
|
} else {
|
|
166
182
|
log.success("Plugin updated!");
|
|
167
183
|
let newVersion = currentVersion;
|
|
@@ -188,15 +204,27 @@ async function updateCommand(opts) {
|
|
|
188
204
|
} else {
|
|
189
205
|
const cmp = semverCompare(cliVersion, latestCliVersion);
|
|
190
206
|
if (cmp === 0) {
|
|
191
|
-
log.success(
|
|
207
|
+
log.success(
|
|
208
|
+
`CLI already on the latest version (${chalk.green(`v${latestCliVersion}`)}).`
|
|
209
|
+
);
|
|
192
210
|
} else if (cmp > 0) {
|
|
193
|
-
log.info(
|
|
211
|
+
log.info(
|
|
212
|
+
`CLI is ahead of latest release (local: ${chalk.cyan(`v${cliVersion}`)}, latest: v${latestCliVersion}). Skipping.`
|
|
213
|
+
);
|
|
194
214
|
} else {
|
|
195
|
-
log.step(
|
|
215
|
+
log.step(
|
|
216
|
+
`CLI update available: ${chalk.yellow(`v${cliVersion}`)} \u2192 ${chalk.green(`v${latestCliVersion}`)}`
|
|
217
|
+
);
|
|
196
218
|
log.step("Updating CLI...");
|
|
197
|
-
const result = run("npm", [
|
|
219
|
+
const result = run("npm", [
|
|
220
|
+
"install",
|
|
221
|
+
"-g",
|
|
222
|
+
"coding-friend-cli@latest"
|
|
223
|
+
]);
|
|
198
224
|
if (result === null) {
|
|
199
|
-
log.error(
|
|
225
|
+
log.error(
|
|
226
|
+
"CLI update failed. Try manually: npm install -g coding-friend-cli@latest"
|
|
227
|
+
);
|
|
200
228
|
} else {
|
|
201
229
|
log.success(`CLI updated to ${chalk.green(`v${latestCliVersion}`)}`);
|
|
202
230
|
}
|
|
@@ -208,7 +236,9 @@ async function updateCommand(opts) {
|
|
|
208
236
|
if (targetVersion) {
|
|
209
237
|
log.step("Updating statusline...");
|
|
210
238
|
if (updateStatusline(targetVersion)) {
|
|
211
|
-
log.success(
|
|
239
|
+
log.success(
|
|
240
|
+
`Statusline updated to ${chalk.green(`v${targetVersion}`)}`
|
|
241
|
+
);
|
|
212
242
|
}
|
|
213
243
|
} else {
|
|
214
244
|
log.warn("No cached plugin version found for statusline update.");
|
|
@@ -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.
|