coding-friend-cli 1.1.1 → 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.
Files changed (106) hide show
  1. package/README.md +15 -0
  2. package/dist/{chunk-KZT4AFDW.js → chunk-5HZJX47M.js} +1 -1
  3. package/dist/{chunk-AQXTNLQD.js → chunk-6OI37OZX.js} +9 -1
  4. package/dist/chunk-R6ZYK4UX.js +128 -0
  5. package/dist/dev-LZASFXZZ.js +243 -0
  6. package/dist/{host-JBTJCWM2.js → host-BK6DYFWF.js} +2 -2
  7. package/dist/index.js +26 -5
  8. package/dist/{init-E6CL3UZQ.js → init-2UKYE2KV.js} +2 -2
  9. package/dist/{mcp-MWESK6UX.js → mcp-CH4SKZSX.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-5A2OP6EY.js} +58 -37
  13. package/lib/learn-host/.prettierignore +3 -0
  14. package/lib/learn-host/.prettierrc +8 -0
  15. package/lib/learn-host/CHANGELOG.md +9 -0
  16. package/lib/learn-host/eslint.config.mjs +6 -0
  17. package/lib/learn-host/next-env.d.ts +1 -1
  18. package/lib/learn-host/next.config.ts +1 -0
  19. package/lib/learn-host/package-lock.json +6039 -391
  20. package/lib/learn-host/package.json +30 -15
  21. package/lib/learn-host/public/_pagefind/fragment/en_1172b3c.pf_fragment +0 -0
  22. package/lib/learn-host/public/_pagefind/fragment/en_118ad1c.pf_fragment +0 -0
  23. package/lib/learn-host/public/_pagefind/fragment/en_32ab3d8.pf_fragment +0 -0
  24. package/lib/learn-host/public/_pagefind/fragment/en_441f1e1.pf_fragment +0 -0
  25. package/lib/learn-host/public/_pagefind/fragment/en_4452de4.pf_fragment +0 -0
  26. package/lib/learn-host/public/_pagefind/fragment/en_4ae396d.pf_fragment +0 -0
  27. package/lib/learn-host/public/_pagefind/fragment/en_58ee89d.pf_fragment +0 -0
  28. package/lib/learn-host/public/_pagefind/fragment/en_6dd2225.pf_fragment +0 -0
  29. package/lib/learn-host/public/_pagefind/fragment/en_765a297.pf_fragment +0 -0
  30. package/lib/learn-host/public/_pagefind/fragment/en_7a4cc4a.pf_fragment +0 -0
  31. package/lib/learn-host/public/_pagefind/fragment/en_8050261.pf_fragment +0 -0
  32. package/lib/learn-host/public/_pagefind/fragment/en_83eaedf.pf_fragment +0 -0
  33. package/lib/learn-host/public/_pagefind/fragment/en_925bc5f.pf_fragment +0 -0
  34. package/lib/learn-host/public/_pagefind/fragment/en_95f3dd5.pf_fragment +0 -0
  35. package/lib/learn-host/public/_pagefind/fragment/en_96d7a02.pf_fragment +0 -0
  36. package/lib/learn-host/public/_pagefind/fragment/en_971f951.pf_fragment +0 -0
  37. package/lib/learn-host/public/_pagefind/fragment/en_a446c32.pf_fragment +0 -0
  38. package/lib/learn-host/public/_pagefind/fragment/en_a5ee367.pf_fragment +0 -0
  39. package/lib/learn-host/public/_pagefind/fragment/en_b11c248.pf_fragment +0 -0
  40. package/lib/learn-host/public/_pagefind/fragment/en_b13c52e.pf_fragment +0 -0
  41. package/lib/learn-host/public/_pagefind/fragment/en_b5bd69b.pf_fragment +0 -0
  42. package/lib/learn-host/public/_pagefind/fragment/en_b625d7d.pf_fragment +0 -0
  43. package/lib/learn-host/public/_pagefind/fragment/en_bf63915.pf_fragment +0 -0
  44. package/lib/learn-host/public/_pagefind/fragment/en_c52b25b.pf_fragment +0 -0
  45. package/lib/learn-host/public/_pagefind/fragment/en_c9db556.pf_fragment +0 -0
  46. package/lib/learn-host/public/_pagefind/fragment/en_d1537ee.pf_fragment +0 -0
  47. package/lib/learn-host/public/_pagefind/fragment/en_d2e6412.pf_fragment +0 -0
  48. package/lib/learn-host/public/_pagefind/fragment/en_d2f47a4.pf_fragment +0 -0
  49. package/lib/learn-host/public/_pagefind/fragment/en_d361292.pf_fragment +0 -0
  50. package/lib/learn-host/public/_pagefind/fragment/en_d727ec8.pf_fragment +0 -0
  51. package/lib/learn-host/public/_pagefind/fragment/en_e11cd8f.pf_fragment +0 -0
  52. package/lib/learn-host/public/_pagefind/fragment/en_e481f19.pf_fragment +0 -0
  53. package/lib/learn-host/public/_pagefind/fragment/en_eee2805.pf_fragment +0 -0
  54. package/lib/learn-host/public/_pagefind/fragment/en_f4de6c4.pf_fragment +0 -0
  55. package/lib/learn-host/public/_pagefind/index/en_1ecb9d5.pf_index +0 -0
  56. package/lib/learn-host/public/_pagefind/index/en_37e362b.pf_index +0 -0
  57. package/lib/learn-host/public/_pagefind/index/en_538eee7.pf_index +0 -0
  58. package/lib/learn-host/public/_pagefind/index/en_5751dc8.pf_index +0 -0
  59. package/lib/learn-host/public/_pagefind/index/en_67f794d.pf_index +0 -0
  60. package/lib/learn-host/public/_pagefind/index/en_7458f81.pf_index +0 -0
  61. package/lib/learn-host/public/_pagefind/index/en_e21f7e1.pf_index +0 -0
  62. package/lib/learn-host/public/_pagefind/pagefind-entry.json +1 -0
  63. package/lib/learn-host/public/_pagefind/pagefind-highlight.js +1064 -0
  64. package/lib/learn-host/public/_pagefind/pagefind-modular-ui.css +214 -0
  65. package/lib/learn-host/public/_pagefind/pagefind-modular-ui.js +8 -0
  66. package/lib/learn-host/public/_pagefind/pagefind-ui.css +1 -0
  67. package/lib/learn-host/public/_pagefind/pagefind-ui.js +2 -0
  68. package/lib/learn-host/public/_pagefind/pagefind.en_104569cceb.pf_meta +0 -0
  69. package/lib/learn-host/public/_pagefind/pagefind.en_1075df6f16.pf_meta +0 -0
  70. package/lib/learn-host/public/_pagefind/pagefind.en_139f35f6e5.pf_meta +0 -0
  71. package/lib/learn-host/public/_pagefind/pagefind.en_46bfc9f7e1.pf_meta +0 -0
  72. package/lib/learn-host/public/_pagefind/pagefind.en_76b8937bbc.pf_meta +0 -0
  73. package/lib/learn-host/public/_pagefind/pagefind.en_83cbfb0fd5.pf_meta +0 -0
  74. package/lib/learn-host/public/_pagefind/pagefind.en_b1d168d536.pf_meta +0 -0
  75. package/lib/learn-host/public/_pagefind/pagefind.js +6 -0
  76. package/lib/learn-host/public/_pagefind/wasm.en.pagefind +0 -0
  77. package/lib/learn-host/public/_pagefind/wasm.unknown.pagefind +0 -0
  78. package/lib/learn-host/public/logo.svg +1 -0
  79. package/lib/learn-host/src/app/[category]/[slug]/page.tsx +36 -32
  80. package/lib/learn-host/src/app/[category]/page.tsx +2 -3
  81. package/lib/learn-host/src/app/apple-icon.svg +1 -0
  82. package/lib/learn-host/src/app/globals.css +74 -14
  83. package/lib/learn-host/src/app/icon.svg +1 -0
  84. package/lib/learn-host/src/app/layout.tsx +29 -9
  85. package/lib/learn-host/src/app/page.tsx +9 -11
  86. package/lib/learn-host/src/components/Breadcrumbs.tsx +12 -4
  87. package/lib/learn-host/src/components/DocCard.tsx +28 -10
  88. package/lib/learn-host/src/components/MarkdownRenderer.tsx +6 -2
  89. package/lib/learn-host/src/components/MobileNav.tsx +43 -35
  90. package/lib/learn-host/src/components/PagefindSearch.tsx +177 -54
  91. package/lib/learn-host/src/components/Sidebar.tsx +27 -29
  92. package/lib/learn-host/src/components/TableOfContents.tsx +62 -0
  93. package/lib/learn-host/src/components/TagBadge.tsx +1 -1
  94. package/lib/learn-host/src/components/ThemeToggle.tsx +36 -9
  95. package/lib/learn-host/src/components/layout/Footer.tsx +41 -0
  96. package/lib/learn-host/src/components/layout/Header.tsx +117 -0
  97. package/lib/learn-host/src/lib/docs.ts +98 -8
  98. package/lib/learn-host/src/lib/types.ts +7 -1
  99. package/lib/learn-host/tsconfig.json +8 -2
  100. package/lib/learn-host/tsconfig.tsbuildinfo +1 -0
  101. package/lib/learn-mcp/CHANGELOG.md +7 -0
  102. package/lib/learn-mcp/package.json +1 -1
  103. package/package.json +13 -5
  104. package/dist/chunk-VHZQ6KEU.js +0 -73
  105. package/lib/learn-host/src/app/search/page.tsx +0 -19
  106. 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
@@ -2,7 +2,7 @@ import {
2
2
  globalConfigPath,
3
3
  localConfigPath,
4
4
  resolvePath
5
- } from "./chunk-AQXTNLQD.js";
5
+ } from "./chunk-6OI37OZX.js";
6
6
  import {
7
7
  readJson
8
8
  } from "./chunk-IUTXHCP7.js";
@@ -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-KZT4AFDW.js";
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-AQXTNLQD.js";
10
+ import "./chunk-6OI37OZX.js";
11
11
  import {
12
12
  log
13
13
  } from "./chunk-6DUFTBTO.js";
package/dist/index.js CHANGED
@@ -12,23 +12,44 @@ var pkg = JSON.parse(
12
12
  var program = new Command();
13
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-E6CL3UZQ.js");
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-JBTJCWM2.js");
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-MWESK6UX.js");
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-7D6YU5YM.js");
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-IH3G4SN5.js");
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-VHZQ6KEU.js";
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-AQXTNLQD.js";
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-KZT4AFDW.js";
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-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-R6ZYK4UX.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";