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
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,17 +66,49 @@ 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
+
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
+
63
94
  ## Publish CLI to npm
64
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
+
65
108
  ```bash
66
- # From the root of coding-friend project
67
109
  cd cli
68
110
  npm login # Login if not already
69
111
  npm publish # Build + bundle + publish
70
-
71
- # To bump a version
72
- npm version patch # 1.0.1 -> 1.0.2
73
- npm version minor # 1.0.1 -> 1.1.0
74
112
  ```
75
113
 
76
114
  `prepublishOnly` runs automatically: builds TypeScript → `dist/` and bundles libs from `lib/`.
@@ -81,4 +119,4 @@ npm version minor # 1.0.1 -> 1.1.0
81
119
 
82
120
  ## License
83
121
 
84
- MIT
122
+ MIT
@@ -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,129 @@
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)
107
+ log.dim(`Tab completion already up-to-date in ~/${rcName}`);
108
+ return false;
109
+ }
110
+ const updated = replaceBlock(content, newBlock);
111
+ writeFileSync(rcPath, updated, "utf-8");
112
+ if (!opts?.silent) {
113
+ log.success(`Tab completion updated in ~/${rcName}`);
114
+ log.dim(`Run \`source ~/${rcName}\` or open a new terminal to activate.`);
115
+ }
116
+ return true;
117
+ }
118
+ appendFileSync(rcPath, newBlock);
119
+ if (!opts?.silent) {
120
+ log.success(`Tab completion added to ~/${rcName}`);
121
+ log.dim(`Run \`source ~/${rcName}\` or open a new terminal to activate.`);
122
+ }
123
+ return true;
124
+ }
125
+
126
+ export {
127
+ hasShellCompletion,
128
+ ensureShellCompletion
129
+ };
@@ -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";
@@ -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 bundled = join(__dirname, "..", "..", "lib", name);
33
- if (existsSync(bundled)) return bundled;
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 or repo.`
35
+ `Could not find lib/${name}. Ensure it exists in the CLI package.`
38
36
  );
39
37
  }
40
38
 
@@ -0,0 +1,290 @@
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 {
21
+ existsSync,
22
+ unlinkSync,
23
+ readdirSync,
24
+ statSync,
25
+ mkdirSync,
26
+ copyFileSync
27
+ } from "fs";
28
+ import { resolve, join } from "path";
29
+ import chalk from "chalk";
30
+ var REMOTE_URL = "https://github.com/dinhanhthi/coding-friend.git";
31
+ var MARKETPLACE_NAME = "coding-friend-marketplace";
32
+ var PLUGIN_NAME = "coding-friend";
33
+ var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
34
+ function getDevState() {
35
+ return readJson(devStatePath());
36
+ }
37
+ function isPluginInstalled() {
38
+ const data = readJson(installedPluginsPath());
39
+ if (!data) return false;
40
+ const plugins = data.plugins ?? data;
41
+ return Object.keys(plugins).some((k) => k.includes(PLUGIN_NAME));
42
+ }
43
+ function isMarketplaceRegistered() {
44
+ const data = readJson(knownMarketplacesPath());
45
+ if (!data) return false;
46
+ return MARKETPLACE_NAME in data;
47
+ }
48
+ function ensureClaude() {
49
+ if (!commandExists("claude")) {
50
+ log.error(
51
+ "Claude CLI not found. Install it first: https://docs.anthropic.com/en/docs/claude-code"
52
+ );
53
+ return false;
54
+ }
55
+ return true;
56
+ }
57
+ function runClaude(args, label) {
58
+ log.step(label);
59
+ const result = run("claude", args);
60
+ if (result === null) {
61
+ log.error(`Failed: claude ${args.join(" ")}`);
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+ async function devOnCommand(path) {
67
+ const state = getDevState();
68
+ if (state) {
69
+ log.warn(`Dev mode is already ON (local: ${chalk.cyan(state.localPath)})`);
70
+ log.dim(`Run ${chalk.bold("cf dev off")} first to switch back.`);
71
+ return;
72
+ }
73
+ if (!ensureClaude()) return;
74
+ const localPath = resolve(path || process.cwd());
75
+ if (!existsSync(resolve(localPath, "plugin", ".claude-plugin", "plugin.json"))) {
76
+ log.error(`No plugin/.claude-plugin/plugin.json found at ${localPath}`);
77
+ log.dim("Make sure you point to the coding-friend repo root.");
78
+ return;
79
+ }
80
+ console.log(`
81
+ === ${chalk.green("Switching to local dev mode")} ===
82
+ `);
83
+ log.info(`Local path: ${chalk.cyan(localPath)}`);
84
+ if (isPluginInstalled()) {
85
+ if (!runClaude(
86
+ ["plugin", "uninstall", PLUGIN_ID],
87
+ "Uninstalling remote plugin..."
88
+ )) {
89
+ run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
90
+ }
91
+ }
92
+ if (isMarketplaceRegistered()) {
93
+ runClaude(
94
+ ["plugin", "marketplace", "remove", MARKETPLACE_NAME],
95
+ "Removing remote marketplace..."
96
+ );
97
+ }
98
+ if (!runClaude(
99
+ ["plugin", "marketplace", "add", localPath],
100
+ "Adding local marketplace..."
101
+ )) {
102
+ log.error("Failed to add local marketplace. Aborting.");
103
+ return;
104
+ }
105
+ if (!runClaude(
106
+ ["plugin", "install", PLUGIN_ID],
107
+ "Installing plugin from local source..."
108
+ )) {
109
+ if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
110
+ log.error("Failed to install local plugin.");
111
+ return;
112
+ }
113
+ }
114
+ const devState = {
115
+ localPath,
116
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
117
+ };
118
+ writeJson(devStatePath(), devState);
119
+ console.log();
120
+ log.success(
121
+ `Dev mode ${chalk.green("ON")} \u2014 using local plugin from ${chalk.cyan(localPath)}`
122
+ );
123
+ log.dim("Restart Claude Code to see changes.");
124
+ }
125
+ async function devOffCommand() {
126
+ const state = getDevState();
127
+ if (!state) {
128
+ log.info("Dev mode is already OFF (using remote marketplace).");
129
+ return;
130
+ }
131
+ if (!ensureClaude()) return;
132
+ console.log(
133
+ `
134
+ === ${chalk.yellow("Switching back to remote marketplace")} ===
135
+ `
136
+ );
137
+ if (isPluginInstalled()) {
138
+ if (!runClaude(
139
+ ["plugin", "uninstall", PLUGIN_ID],
140
+ "Uninstalling local plugin..."
141
+ )) {
142
+ run("claude", ["plugin", "uninstall", PLUGIN_NAME]);
143
+ }
144
+ }
145
+ if (isMarketplaceRegistered()) {
146
+ runClaude(
147
+ ["plugin", "marketplace", "remove", MARKETPLACE_NAME],
148
+ "Removing local marketplace..."
149
+ );
150
+ }
151
+ if (!runClaude(
152
+ ["plugin", "marketplace", "add", REMOTE_URL],
153
+ "Adding remote marketplace..."
154
+ )) {
155
+ log.error("Failed to add remote marketplace.");
156
+ return;
157
+ }
158
+ if (!runClaude(
159
+ ["plugin", "install", PLUGIN_ID],
160
+ "Installing plugin from remote..."
161
+ )) {
162
+ if (!runClaude(["plugin", "install", PLUGIN_NAME], "Retrying install...")) {
163
+ log.error("Failed to install remote plugin.");
164
+ return;
165
+ }
166
+ }
167
+ try {
168
+ unlinkSync(devStatePath());
169
+ } catch {
170
+ }
171
+ console.log();
172
+ log.success(`Dev mode ${chalk.yellow("OFF")} \u2014 using remote marketplace.`);
173
+ log.dim("Restart Claude Code to see changes.");
174
+ }
175
+ function getMarketplaceSource() {
176
+ const data = readJson(
177
+ knownMarketplacesPath()
178
+ );
179
+ if (!data || !(MARKETPLACE_NAME in data)) return null;
180
+ const entry = data[MARKETPLACE_NAME];
181
+ const src = entry.source;
182
+ if (!src) return null;
183
+ if (src.source === "directory" && src.path) {
184
+ return { type: "local", location: src.path };
185
+ }
186
+ if (src.source === "github" && src.repo) {
187
+ return { type: "remote", location: src.repo };
188
+ }
189
+ return null;
190
+ }
191
+ function copyDirRecursive(src, dest, fileCount = { n: 0 }) {
192
+ if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
193
+ for (const entry of readdirSync(src)) {
194
+ const srcPath = join(src, entry);
195
+ const destPath = join(dest, entry);
196
+ if (statSync(srcPath).isDirectory()) {
197
+ copyDirRecursive(srcPath, destPath, fileCount);
198
+ } else {
199
+ copyFileSync(srcPath, destPath);
200
+ fileCount.n++;
201
+ }
202
+ }
203
+ }
204
+ async function devSyncCommand() {
205
+ const state = getDevState();
206
+ if (!state) {
207
+ log.error("Dev mode is OFF. Run `cf dev on <path>` first.");
208
+ return;
209
+ }
210
+ const localPath = state.localPath;
211
+ const pluginSrcDir = join(localPath, "plugin");
212
+ if (!existsSync(pluginSrcDir)) {
213
+ log.error(
214
+ `No plugin/ directory found at ${localPath}. Make sure you point to the coding-friend repo root.`
215
+ );
216
+ return;
217
+ }
218
+ const cacheBase = pluginCachePath();
219
+ let cacheVersionDir = null;
220
+ if (existsSync(cacheBase)) {
221
+ const versions = readdirSync(cacheBase).filter(
222
+ (v) => statSync(join(cacheBase, v)).isDirectory()
223
+ );
224
+ if (versions.length > 0) {
225
+ cacheVersionDir = join(
226
+ cacheBase,
227
+ versions.sort((a, b) => {
228
+ return statSync(join(cacheBase, b)).mtimeMs - statSync(join(cacheBase, a)).mtimeMs;
229
+ })[0]
230
+ );
231
+ }
232
+ }
233
+ if (!cacheVersionDir) {
234
+ log.error(
235
+ "No cached plugin version found. Run `cf dev off && cf dev on` first."
236
+ );
237
+ return;
238
+ }
239
+ const shortDest = cacheVersionDir.replace(process.env.HOME ?? "", "~");
240
+ log.step(`Syncing ${chalk.cyan(pluginSrcDir)} \u2192 ${chalk.dim(shortDest)}`);
241
+ const fileCount = { n: 0 };
242
+ copyDirRecursive(pluginSrcDir, cacheVersionDir, fileCount);
243
+ log.success(
244
+ `Synced ${chalk.green(fileCount.n)} files. Restart Claude Code to apply changes.`
245
+ );
246
+ }
247
+ async function devRestartCommand(path) {
248
+ const state = getDevState();
249
+ if (!ensureClaude()) return;
250
+ const localPath = path ?? state?.localPath;
251
+ console.log(`
252
+ === ${chalk.cyan("Restarting dev mode")} ===
253
+ `);
254
+ if (state) {
255
+ await devOffCommand();
256
+ console.log();
257
+ } else {
258
+ log.info("Dev mode was OFF \u2014 skipping off step.");
259
+ }
260
+ await devOnCommand(localPath);
261
+ }
262
+ async function devStatusCommand() {
263
+ const state = getDevState();
264
+ const source = getMarketplaceSource();
265
+ const installed = isPluginInstalled();
266
+ if (state) {
267
+ log.info(`Dev mode: ${chalk.green("ON")}`);
268
+ log.info(`Local path: ${chalk.cyan(state.localPath)}`);
269
+ log.dim(`Since: ${state.savedAt}`);
270
+ } else {
271
+ log.info(`Dev mode: ${chalk.yellow("OFF")}`);
272
+ }
273
+ console.log();
274
+ if (source) {
275
+ const label = source.type === "local" ? `${chalk.green("local")} \u2192 ${chalk.cyan(source.location)}` : `${chalk.blue("remote")} \u2192 ${source.location}`;
276
+ log.info(`Marketplace source: ${label}`);
277
+ } else {
278
+ log.warn(`Marketplace "${MARKETPLACE_NAME}" not registered.`);
279
+ }
280
+ log.info(
281
+ `Plugin installed: ${installed ? chalk.green("yes") : chalk.yellow("no")}`
282
+ );
283
+ }
284
+ export {
285
+ devOffCommand,
286
+ devOnCommand,
287
+ devRestartCommand,
288
+ devStatusCommand,
289
+ devSyncCommand
290
+ };
@@ -1,13 +1,13 @@
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
  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
@@ -10,25 +10,63 @@ 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, "-v, --version");
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-E6CL3UZQ.js");
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-JBTJCWM2.js");
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-MWESK6UX.js");
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 () => {
27
- const { statuslineCommand } = await import("./statusline-7D6YU5YM.js");
29
+ const { statuslineCommand } = await import("./statusline-ARI7I5YM.js");
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-IH3G4SN5.js");
33
+ const { updateCommand } = await import("./update-GGCBM7U4.js");
32
34
  await updateCommand(opts);
33
35
  });
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
+ );
47
+ dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
48
+ const { devOnCommand } = await import("./dev-MAAWPWML.js");
49
+ await devOnCommand(path);
50
+ });
51
+ dev.command("off").description("Switch back to remote marketplace").action(async () => {
52
+ const { devOffCommand } = await import("./dev-MAAWPWML.js");
53
+ await devOffCommand();
54
+ });
55
+ dev.command("status").description("Show current dev mode").action(async () => {
56
+ const { devStatusCommand } = await import("./dev-MAAWPWML.js");
57
+ await devStatusCommand();
58
+ });
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");
63
+ await devSyncCommand();
64
+ });
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");
70
+ await devRestartCommand(path);
71
+ });
34
72
  program.parse();