coding-friend-cli 1.1.0 → 1.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 +15 -0
- package/dist/{chunk-KZT4AFDW.js → chunk-5HZJX47M.js} +1 -1
- package/dist/{chunk-AQXTNLQD.js → chunk-6OI37OZX.js} +9 -1
- package/dist/chunk-R6ZYK4UX.js +128 -0
- package/dist/dev-LZASFXZZ.js +243 -0
- package/dist/{host-JBTJCWM2.js → host-BK6DYFWF.js} +2 -2
- package/dist/index.js +27 -6
- package/dist/{init-E6CL3UZQ.js → init-2UKYE2KV.js} +2 -2
- package/dist/{mcp-MWESK6UX.js → mcp-CH4SKZSX.js} +2 -2
- package/dist/postinstall.js +1 -1
- package/dist/{statusline-7D6YU5YM.js → statusline-ARI7I5YM.js} +1 -1
- package/dist/{update-IH3G4SN5.js → update-5A2OP6EY.js} +58 -37
- package/lib/learn-host/.prettierignore +3 -0
- package/lib/learn-host/.prettierrc +8 -0
- package/lib/learn-host/CHANGELOG.md +9 -0
- package/lib/learn-host/eslint.config.mjs +6 -0
- package/lib/learn-host/next-env.d.ts +1 -1
- package/lib/learn-host/next.config.ts +1 -0
- package/lib/learn-host/package-lock.json +6039 -391
- package/lib/learn-host/package.json +30 -15
- 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 +1 -0
- package/lib/learn-host/public/_pagefind/pagefind-highlight.js +1064 -0
- package/lib/learn-host/public/_pagefind/pagefind-modular-ui.css +214 -0
- package/lib/learn-host/public/_pagefind/pagefind-modular-ui.js +8 -0
- package/lib/learn-host/public/_pagefind/pagefind-ui.css +1 -0
- package/lib/learn-host/public/_pagefind/pagefind-ui.js +2 -0
- 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 +6 -0
- package/lib/learn-host/public/_pagefind/wasm.en.pagefind +0 -0
- package/lib/learn-host/public/_pagefind/wasm.unknown.pagefind +0 -0
- package/lib/learn-host/public/logo.svg +1 -0
- package/lib/learn-host/src/app/[category]/[slug]/page.tsx +36 -32
- package/lib/learn-host/src/app/[category]/page.tsx +2 -3
- package/lib/learn-host/src/app/apple-icon.svg +1 -0
- package/lib/learn-host/src/app/globals.css +74 -14
- package/lib/learn-host/src/app/icon.svg +1 -0
- package/lib/learn-host/src/app/layout.tsx +29 -9
- package/lib/learn-host/src/app/page.tsx +9 -11
- package/lib/learn-host/src/components/Breadcrumbs.tsx +12 -4
- package/lib/learn-host/src/components/DocCard.tsx +28 -10
- package/lib/learn-host/src/components/MarkdownRenderer.tsx +6 -2
- package/lib/learn-host/src/components/MobileNav.tsx +43 -35
- package/lib/learn-host/src/components/PagefindSearch.tsx +177 -54
- package/lib/learn-host/src/components/Sidebar.tsx +27 -29
- package/lib/learn-host/src/components/TableOfContents.tsx +62 -0
- package/lib/learn-host/src/components/TagBadge.tsx +1 -1
- package/lib/learn-host/src/components/ThemeToggle.tsx +36 -9
- package/lib/learn-host/src/components/layout/Footer.tsx +41 -0
- package/lib/learn-host/src/components/layout/Header.tsx +117 -0
- package/lib/learn-host/src/lib/docs.ts +98 -8
- package/lib/learn-host/src/lib/types.ts +7 -1
- package/lib/learn-host/tsconfig.json +8 -2
- package/lib/learn-host/tsconfig.tsbuildinfo +1 -0
- package/lib/learn-mcp/CHANGELOG.md +7 -0
- package/lib/learn-mcp/package.json +1 -1
- package/package.json +13 -5
- package/dist/chunk-VHZQ6KEU.js +0 -73
- package/lib/learn-host/src/app/search/page.tsx +0 -19
- package/lib/learn-host/src/components/SearchBar.tsx +0 -36
package/README.md
CHANGED
|
@@ -28,6 +28,11 @@ cf update # Update plugin + CLI + statusline
|
|
|
28
28
|
cf update --cli # Update only the CLI (npm package)
|
|
29
29
|
cf update --plugin # Update only the Claude Code plugin
|
|
30
30
|
cf update --statusline # Update only the statusline
|
|
31
|
+
cf dev on [path] # Switch to local plugin source for development
|
|
32
|
+
cf dev off # Switch back to remote marketplace
|
|
33
|
+
cf dev status # Show current dev mode (local or remote)
|
|
34
|
+
cf dev sync # Sync local changes to cache (no version bump needed)
|
|
35
|
+
cf dev restart # Reinstall local dev plugin (off + on)
|
|
31
36
|
cf help # Show all commands
|
|
32
37
|
```
|
|
33
38
|
|
|
@@ -46,6 +51,7 @@ Now `cf` is available globally, pointing to your local source. After making chan
|
|
|
46
51
|
|
|
47
52
|
```bash
|
|
48
53
|
npm run build # Rebuild (no need to re-link)
|
|
54
|
+
npm run watch # Auto-rebuild on file changes
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
For development without rebuilding:
|
|
@@ -60,6 +66,15 @@ To unlink when done:
|
|
|
60
66
|
npm unlink -g coding-friend-cli
|
|
61
67
|
```
|
|
62
68
|
|
|
69
|
+
To check if `cf` is pointing to the local plugin source:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm ls -g coding-friend-cli
|
|
73
|
+
# Result:
|
|
74
|
+
# /Users/thi/.nvm/versions/node/v22.21.1/lib
|
|
75
|
+
#└── coding-friend-cli@1.1.1 -> ./../../../../../git/coding-friend/cli
|
|
76
|
+
```
|
|
77
|
+
|
|
63
78
|
## Publish CLI to npm
|
|
64
79
|
|
|
65
80
|
```bash
|
|
@@ -28,6 +28,12 @@ function pluginCachePath() {
|
|
|
28
28
|
"coding-friend"
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
|
+
function devStatePath() {
|
|
32
|
+
return join(homedir(), ".coding-friend", "dev-state.json");
|
|
33
|
+
}
|
|
34
|
+
function knownMarketplacesPath() {
|
|
35
|
+
return join(homedir(), ".claude", "plugins", "known_marketplaces.json");
|
|
36
|
+
}
|
|
31
37
|
|
|
32
38
|
export {
|
|
33
39
|
resolvePath,
|
|
@@ -35,5 +41,7 @@ export {
|
|
|
35
41
|
globalConfigPath,
|
|
36
42
|
claudeSettingsPath,
|
|
37
43
|
installedPluginsPath,
|
|
38
|
-
pluginCachePath
|
|
44
|
+
pluginCachePath,
|
|
45
|
+
devStatePath,
|
|
46
|
+
knownMarketplacesPath
|
|
39
47
|
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
log
|
|
3
|
+
} from "./chunk-6DUFTBTO.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/shell-completion.ts
|
|
6
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
var MARKER_START = "# >>> coding-friend CLI completion >>>";
|
|
9
|
+
var MARKER_END = "# <<< coding-friend CLI completion <<<";
|
|
10
|
+
var BASH_BLOCK = `
|
|
11
|
+
|
|
12
|
+
${MARKER_START}
|
|
13
|
+
_cf_completions() {
|
|
14
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
15
|
+
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
16
|
+
local commands="init host mcp statusline update dev"
|
|
17
|
+
|
|
18
|
+
# Subcommands for 'dev'
|
|
19
|
+
if [[ "\${COMP_WORDS[1]}" == "dev" && \${COMP_CWORD} -eq 2 ]]; then
|
|
20
|
+
COMPREPLY=($(compgen -W "on off status restart sync" -- "$cur"))
|
|
21
|
+
return
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Path completion for 'dev on'
|
|
25
|
+
if [[ "\${COMP_WORDS[1]}" == "dev" && "$prev" == "on" ]]; then
|
|
26
|
+
COMPREPLY=($(compgen -d -- "$cur"))
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
31
|
+
}
|
|
32
|
+
complete -o default -F _cf_completions cf
|
|
33
|
+
${MARKER_END}
|
|
34
|
+
`;
|
|
35
|
+
var ZSH_BLOCK = `
|
|
36
|
+
|
|
37
|
+
${MARKER_START}
|
|
38
|
+
_cf() {
|
|
39
|
+
local -a commands
|
|
40
|
+
commands=(
|
|
41
|
+
'init:Initialize coding-friend in current project'
|
|
42
|
+
'host:Build and serve learning docs as a static website'
|
|
43
|
+
'mcp:Setup MCP server for learning docs'
|
|
44
|
+
'statusline:Setup coding-friend statusline in Claude Code'
|
|
45
|
+
'update:Update coding-friend plugin and refresh statusline'
|
|
46
|
+
'dev:Switch between local and remote plugin for development'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if (( CURRENT == 2 )); then
|
|
50
|
+
_describe 'command' commands
|
|
51
|
+
elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "dev" ]]; then
|
|
52
|
+
local -a subcommands
|
|
53
|
+
subcommands=(
|
|
54
|
+
'on:Switch to local plugin source'
|
|
55
|
+
'off:Switch back to remote marketplace'
|
|
56
|
+
'status:Show current dev mode'
|
|
57
|
+
'restart:Restart dev mode (re-apply local plugin)'
|
|
58
|
+
'sync:Sync local plugin files without restarting'
|
|
59
|
+
)
|
|
60
|
+
_describe 'subcommand' subcommands
|
|
61
|
+
elif (( CURRENT == 4 )) && [[ "\${words[2]}" == "dev" && "\${words[3]}" == "on" ]]; then
|
|
62
|
+
_path_files -/
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
compdef _cf cf
|
|
66
|
+
${MARKER_END}
|
|
67
|
+
`;
|
|
68
|
+
function getShellRcPath() {
|
|
69
|
+
const shell = process.env.SHELL ?? "";
|
|
70
|
+
if (shell.includes("zsh")) return `${homedir()}/.zshrc`;
|
|
71
|
+
return `${homedir()}/.bashrc`;
|
|
72
|
+
}
|
|
73
|
+
function getRcName(rcPath) {
|
|
74
|
+
return rcPath.endsWith(".zshrc") ? ".zshrc" : ".bashrc";
|
|
75
|
+
}
|
|
76
|
+
function isZsh(rcPath) {
|
|
77
|
+
return rcPath.endsWith(".zshrc");
|
|
78
|
+
}
|
|
79
|
+
function hasShellCompletion() {
|
|
80
|
+
const rcPath = getShellRcPath();
|
|
81
|
+
if (!existsSync(rcPath)) return false;
|
|
82
|
+
return readFileSync(rcPath, "utf-8").includes(MARKER_START);
|
|
83
|
+
}
|
|
84
|
+
function extractExistingBlock(content) {
|
|
85
|
+
const startIdx = content.indexOf(MARKER_START);
|
|
86
|
+
const endIdx = content.indexOf(MARKER_END);
|
|
87
|
+
if (startIdx === -1 || endIdx === -1) return null;
|
|
88
|
+
return content.slice(startIdx, endIdx + MARKER_END.length);
|
|
89
|
+
}
|
|
90
|
+
function replaceBlock(content, newBlock) {
|
|
91
|
+
const startIdx = content.indexOf(MARKER_START);
|
|
92
|
+
const endIdx = content.indexOf(MARKER_END);
|
|
93
|
+
let sliceStart = startIdx;
|
|
94
|
+
while (sliceStart > 0 && content[sliceStart - 1] === "\n") sliceStart--;
|
|
95
|
+
return content.slice(0, sliceStart) + newBlock + content.slice(endIdx + MARKER_END.length);
|
|
96
|
+
}
|
|
97
|
+
function ensureShellCompletion(opts) {
|
|
98
|
+
const rcPath = getShellRcPath();
|
|
99
|
+
const rcName = getRcName(rcPath);
|
|
100
|
+
const newBlock = isZsh(rcPath) ? ZSH_BLOCK : BASH_BLOCK;
|
|
101
|
+
if (hasShellCompletion()) {
|
|
102
|
+
const content = readFileSync(rcPath, "utf-8");
|
|
103
|
+
const existing = extractExistingBlock(content);
|
|
104
|
+
const expectedBlock = newBlock.trim();
|
|
105
|
+
if (existing && existing.trim() === expectedBlock) {
|
|
106
|
+
if (!opts?.silent) log.dim(`Tab completion already up-to-date in ~/${rcName}`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const updated = replaceBlock(content, newBlock);
|
|
110
|
+
writeFileSync(rcPath, updated, "utf-8");
|
|
111
|
+
if (!opts?.silent) {
|
|
112
|
+
log.success(`Tab completion updated in ~/${rcName}`);
|
|
113
|
+
log.dim(`Run \`source ~/${rcName}\` or open a new terminal to activate.`);
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
appendFileSync(rcPath, newBlock);
|
|
118
|
+
if (!opts?.silent) {
|
|
119
|
+
log.success(`Tab completion added to ~/${rcName}`);
|
|
120
|
+
log.dim(`Run \`source ~/${rcName}\` or open a new terminal to activate.`);
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
hasShellCompletion,
|
|
127
|
+
ensureShellCompletion
|
|
128
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commandExists,
|
|
3
|
+
run
|
|
4
|
+
} from "./chunk-UFGNO6CW.js";
|
|
5
|
+
import {
|
|
6
|
+
devStatePath,
|
|
7
|
+
installedPluginsPath,
|
|
8
|
+
knownMarketplacesPath,
|
|
9
|
+
pluginCachePath
|
|
10
|
+
} from "./chunk-6OI37OZX.js";
|
|
11
|
+
import {
|
|
12
|
+
log
|
|
13
|
+
} from "./chunk-6DUFTBTO.js";
|
|
14
|
+
import {
|
|
15
|
+
readJson,
|
|
16
|
+
writeJson
|
|
17
|
+
} from "./chunk-IUTXHCP7.js";
|
|
18
|
+
|
|
19
|
+
// src/commands/dev.ts
|
|
20
|
+
import { existsSync, unlinkSync, readdirSync, statSync, mkdirSync, copyFileSync } from "fs";
|
|
21
|
+
import { resolve, join } from "path";
|
|
22
|
+
import chalk from "chalk";
|
|
23
|
+
var REMOTE_URL = "https://github.com/dinhanhthi/coding-friend.git";
|
|
24
|
+
var MARKETPLACE_NAME = "coding-friend-marketplace";
|
|
25
|
+
var PLUGIN_NAME = "coding-friend";
|
|
26
|
+
var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
27
|
+
function getDevState() {
|
|
28
|
+
return readJson(devStatePath());
|
|
29
|
+
}
|
|
30
|
+
function isPluginInstalled() {
|
|
31
|
+
const data = readJson(installedPluginsPath());
|
|
32
|
+
if (!data) return false;
|
|
33
|
+
const plugins = data.plugins ?? data;
|
|
34
|
+
return Object.keys(plugins).some((k) => k.includes(PLUGIN_NAME));
|
|
35
|
+
}
|
|
36
|
+
function isMarketplaceRegistered() {
|
|
37
|
+
const data = readJson(knownMarketplacesPath());
|
|
38
|
+
if (!data) return false;
|
|
39
|
+
return MARKETPLACE_NAME in data;
|
|
40
|
+
}
|
|
41
|
+
function ensureClaude() {
|
|
42
|
+
if (!commandExists("claude")) {
|
|
43
|
+
log.error("Claude CLI not found. Install it first: https://docs.anthropic.com/en/docs/claude-code");
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
function runClaude(args, label) {
|
|
49
|
+
log.step(label);
|
|
50
|
+
const result = run("claude", args);
|
|
51
|
+
if (result === null) {
|
|
52
|
+
log.error(`Failed: claude ${args.join(" ")}`);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
async function devOnCommand(path) {
|
|
58
|
+
const state = getDevState();
|
|
59
|
+
if (state) {
|
|
60
|
+
log.warn(`Dev mode is already ON (local: ${chalk.cyan(state.localPath)})`);
|
|
61
|
+
log.dim(`Run ${chalk.bold("cf dev off")} first to switch back.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!ensureClaude()) return;
|
|
65
|
+
const localPath = resolve(path || process.cwd());
|
|
66
|
+
if (!existsSync(resolve(localPath, "plugin", ".claude-plugin", "plugin.json"))) {
|
|
67
|
+
log.error(`No plugin/.claude-plugin/plugin.json found at ${localPath}`);
|
|
68
|
+
log.dim("Make sure you point to the coding-friend repo root.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(`
|
|
72
|
+
=== ${chalk.green("Switching to local dev mode")} ===
|
|
73
|
+
`);
|
|
74
|
+
log.info(`Local path: ${chalk.cyan(localPath)}`);
|
|
75
|
+
if (isPluginInstalled()) {
|
|
76
|
+
if (!runClaude(["plugin", "uninstall", PLUGIN_ID], "Uninstalling remote plugin...")) {
|
|
77
|
+
run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (isMarketplaceRegistered()) {
|
|
81
|
+
runClaude(["plugin", "marketplace", "remove", MARKETPLACE_NAME], "Removing remote marketplace...");
|
|
82
|
+
}
|
|
83
|
+
if (!runClaude(["plugin", "marketplace", "add", localPath], "Adding local marketplace...")) {
|
|
84
|
+
log.error("Failed to add local marketplace. Aborting.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!runClaude(["plugin", "install", PLUGIN_ID], "Installing plugin from local source...")) {
|
|
88
|
+
if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
|
|
89
|
+
log.error("Failed to install local plugin.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const devState = {
|
|
94
|
+
localPath,
|
|
95
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
96
|
+
};
|
|
97
|
+
writeJson(devStatePath(), devState);
|
|
98
|
+
console.log();
|
|
99
|
+
log.success(`Dev mode ${chalk.green("ON")} \u2014 using local plugin from ${chalk.cyan(localPath)}`);
|
|
100
|
+
log.dim("Restart Claude Code to see changes.");
|
|
101
|
+
}
|
|
102
|
+
async function devOffCommand() {
|
|
103
|
+
const state = getDevState();
|
|
104
|
+
if (!state) {
|
|
105
|
+
log.info("Dev mode is already OFF (using remote marketplace).");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!ensureClaude()) return;
|
|
109
|
+
console.log(`
|
|
110
|
+
=== ${chalk.yellow("Switching back to remote marketplace")} ===
|
|
111
|
+
`);
|
|
112
|
+
if (isPluginInstalled()) {
|
|
113
|
+
if (!runClaude(["plugin", "uninstall", PLUGIN_ID], "Uninstalling local plugin...")) {
|
|
114
|
+
run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (isMarketplaceRegistered()) {
|
|
118
|
+
runClaude(["plugin", "marketplace", "remove", MARKETPLACE_NAME], "Removing local marketplace...");
|
|
119
|
+
}
|
|
120
|
+
if (!runClaude(["plugin", "marketplace", "add", REMOTE_URL], "Adding remote marketplace...")) {
|
|
121
|
+
log.error("Failed to add remote marketplace.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!runClaude(["plugin", "install", PLUGIN_ID], "Installing plugin from remote...")) {
|
|
125
|
+
if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
|
|
126
|
+
log.error("Failed to install remote plugin.");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
unlinkSync(devStatePath());
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
console.log();
|
|
135
|
+
log.success(`Dev mode ${chalk.yellow("OFF")} \u2014 using remote marketplace.`);
|
|
136
|
+
log.dim("Restart Claude Code to see changes.");
|
|
137
|
+
}
|
|
138
|
+
function getMarketplaceSource() {
|
|
139
|
+
const data = readJson(knownMarketplacesPath());
|
|
140
|
+
if (!data || !(MARKETPLACE_NAME in data)) return null;
|
|
141
|
+
const entry = data[MARKETPLACE_NAME];
|
|
142
|
+
const src = entry.source;
|
|
143
|
+
if (!src) return null;
|
|
144
|
+
if (src.source === "directory" && src.path) {
|
|
145
|
+
return { type: "local", location: src.path };
|
|
146
|
+
}
|
|
147
|
+
if (src.source === "github" && src.repo) {
|
|
148
|
+
return { type: "remote", location: src.repo };
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function copyDirRecursive(src, dest, fileCount = { n: 0 }) {
|
|
153
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
154
|
+
for (const entry of readdirSync(src)) {
|
|
155
|
+
const srcPath = join(src, entry);
|
|
156
|
+
const destPath = join(dest, entry);
|
|
157
|
+
if (statSync(srcPath).isDirectory()) {
|
|
158
|
+
copyDirRecursive(srcPath, destPath, fileCount);
|
|
159
|
+
} else {
|
|
160
|
+
copyFileSync(srcPath, destPath);
|
|
161
|
+
fileCount.n++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function devSyncCommand() {
|
|
166
|
+
const state = getDevState();
|
|
167
|
+
if (!state) {
|
|
168
|
+
log.error("Dev mode is OFF. Run `cf dev on <path>` first.");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const localPath = state.localPath;
|
|
172
|
+
const pluginSrcDir = join(localPath, "plugin");
|
|
173
|
+
if (!existsSync(pluginSrcDir)) {
|
|
174
|
+
log.error(`No plugin/ directory found at ${localPath}. Make sure you point to the coding-friend repo root.`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const cacheBase = pluginCachePath();
|
|
178
|
+
let cacheVersionDir = null;
|
|
179
|
+
if (existsSync(cacheBase)) {
|
|
180
|
+
const versions = readdirSync(cacheBase).filter(
|
|
181
|
+
(v) => statSync(join(cacheBase, v)).isDirectory()
|
|
182
|
+
);
|
|
183
|
+
if (versions.length > 0) {
|
|
184
|
+
cacheVersionDir = join(
|
|
185
|
+
cacheBase,
|
|
186
|
+
versions.sort((a, b) => {
|
|
187
|
+
return statSync(join(cacheBase, b)).mtimeMs - statSync(join(cacheBase, a)).mtimeMs;
|
|
188
|
+
})[0]
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!cacheVersionDir) {
|
|
193
|
+
log.error("No cached plugin version found. Run `cf dev off && cf dev on` first.");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const shortDest = cacheVersionDir.replace(process.env.HOME ?? "", "~");
|
|
197
|
+
log.step(`Syncing ${chalk.cyan(pluginSrcDir)} \u2192 ${chalk.dim(shortDest)}`);
|
|
198
|
+
const fileCount = { n: 0 };
|
|
199
|
+
copyDirRecursive(pluginSrcDir, cacheVersionDir, fileCount);
|
|
200
|
+
log.success(`Synced ${chalk.green(fileCount.n)} files. Restart Claude Code to apply changes.`);
|
|
201
|
+
}
|
|
202
|
+
async function devRestartCommand(path) {
|
|
203
|
+
const state = getDevState();
|
|
204
|
+
if (!ensureClaude()) return;
|
|
205
|
+
const localPath = path ?? state?.localPath;
|
|
206
|
+
console.log(`
|
|
207
|
+
=== ${chalk.cyan("Restarting dev mode")} ===
|
|
208
|
+
`);
|
|
209
|
+
if (state) {
|
|
210
|
+
await devOffCommand();
|
|
211
|
+
console.log();
|
|
212
|
+
} else {
|
|
213
|
+
log.info("Dev mode was OFF \u2014 skipping off step.");
|
|
214
|
+
}
|
|
215
|
+
await devOnCommand(localPath);
|
|
216
|
+
}
|
|
217
|
+
async function devStatusCommand() {
|
|
218
|
+
const state = getDevState();
|
|
219
|
+
const source = getMarketplaceSource();
|
|
220
|
+
const installed = isPluginInstalled();
|
|
221
|
+
if (state) {
|
|
222
|
+
log.info(`Dev mode: ${chalk.green("ON")}`);
|
|
223
|
+
log.info(`Local path: ${chalk.cyan(state.localPath)}`);
|
|
224
|
+
log.dim(`Since: ${state.savedAt}`);
|
|
225
|
+
} else {
|
|
226
|
+
log.info(`Dev mode: ${chalk.yellow("OFF")}`);
|
|
227
|
+
}
|
|
228
|
+
console.log();
|
|
229
|
+
if (source) {
|
|
230
|
+
const label = source.type === "local" ? `${chalk.green("local")} \u2192 ${chalk.cyan(source.location)}` : `${chalk.blue("remote")} \u2192 ${source.location}`;
|
|
231
|
+
log.info(`Marketplace source: ${label}`);
|
|
232
|
+
} else {
|
|
233
|
+
log.warn(`Marketplace "${MARKETPLACE_NAME}" not registered.`);
|
|
234
|
+
}
|
|
235
|
+
log.info(`Plugin installed: ${installed ? chalk.green("yes") : chalk.yellow("no")}`);
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
devOffCommand,
|
|
239
|
+
devOnCommand,
|
|
240
|
+
devRestartCommand,
|
|
241
|
+
devStatusCommand,
|
|
242
|
+
devSyncCommand
|
|
243
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLibPath,
|
|
3
3
|
resolveDocsDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5HZJX47M.js";
|
|
5
5
|
import "./chunk-HRVSKMNA.js";
|
|
6
6
|
import {
|
|
7
7
|
run,
|
|
8
8
|
streamExec
|
|
9
9
|
} from "./chunk-UFGNO6CW.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-6OI37OZX.js";
|
|
11
11
|
import {
|
|
12
12
|
log
|
|
13
13
|
} from "./chunk-6DUFTBTO.js";
|
package/dist/index.js
CHANGED
|
@@ -10,25 +10,46 @@ 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("coding-friend CLI \u2014 host learning docs, setup MCP, init projects").version(pkg.version);
|
|
13
|
+
program.name("cf").description("coding-friend CLI \u2014 host learning docs, setup MCP, init projects").version(pkg.version, "-v, --version");
|
|
14
14
|
program.command("init").description("Initialize coding-friend in current project").action(async () => {
|
|
15
|
-
const { initCommand } = await import("./init-
|
|
15
|
+
const { initCommand } = await import("./init-2UKYE2KV.js");
|
|
16
16
|
await initCommand();
|
|
17
17
|
});
|
|
18
18
|
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-
|
|
19
|
+
const { hostCommand } = await import("./host-BK6DYFWF.js");
|
|
20
20
|
await hostCommand(path, opts);
|
|
21
21
|
});
|
|
22
22
|
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-
|
|
23
|
+
const { mcpCommand } = await import("./mcp-CH4SKZSX.js");
|
|
24
24
|
await mcpCommand(path);
|
|
25
25
|
});
|
|
26
26
|
program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
|
|
27
|
-
const { statuslineCommand } = await import("./statusline-
|
|
27
|
+
const { statuslineCommand } = await import("./statusline-ARI7I5YM.js");
|
|
28
28
|
await statuslineCommand();
|
|
29
29
|
});
|
|
30
30
|
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-
|
|
31
|
+
const { updateCommand } = await import("./update-5A2OP6EY.js");
|
|
32
32
|
await updateCommand(opts);
|
|
33
33
|
});
|
|
34
|
+
var dev = program.command("dev").description("Switch between local and remote plugin for development");
|
|
35
|
+
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-LZASFXZZ.js");
|
|
37
|
+
await devOnCommand(path);
|
|
38
|
+
});
|
|
39
|
+
dev.command("off").description("Switch back to remote marketplace").action(async () => {
|
|
40
|
+
const { devOffCommand } = await import("./dev-LZASFXZZ.js");
|
|
41
|
+
await devOffCommand();
|
|
42
|
+
});
|
|
43
|
+
dev.command("status").description("Show current dev mode").action(async () => {
|
|
44
|
+
const { devStatusCommand } = await import("./dev-LZASFXZZ.js");
|
|
45
|
+
await devStatusCommand();
|
|
46
|
+
});
|
|
47
|
+
dev.command("sync").description("Copy local source files to plugin cache (no version bump needed)").action(async () => {
|
|
48
|
+
const { devSyncCommand } = await import("./dev-LZASFXZZ.js");
|
|
49
|
+
await devSyncCommand();
|
|
50
|
+
});
|
|
51
|
+
dev.command("restart").description("Reinstall local dev plugin (off + on)").argument("[path]", "path to local coding-friend repo (default: saved path or cwd)").action(async (path) => {
|
|
52
|
+
const { devRestartCommand } = await import("./dev-LZASFXZZ.js");
|
|
53
|
+
await devRestartCommand(path);
|
|
54
|
+
});
|
|
34
55
|
program.parse();
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
ensureShellCompletion,
|
|
6
6
|
hasShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-R6ZYK4UX.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-
|
|
16
|
+
} from "./chunk-6OI37OZX.js";
|
|
17
17
|
import {
|
|
18
18
|
log
|
|
19
19
|
} from "./chunk-6DUFTBTO.js";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLibPath,
|
|
3
3
|
resolveDocsDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5HZJX47M.js";
|
|
5
5
|
import "./chunk-HRVSKMNA.js";
|
|
6
6
|
import {
|
|
7
7
|
run
|
|
8
8
|
} from "./chunk-UFGNO6CW.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-6OI37OZX.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
12
|
} from "./chunk-6DUFTBTO.js";
|
package/dist/postinstall.js
CHANGED