coding-friend-cli 1.10.0 → 1.12.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 CHANGED
@@ -16,10 +16,29 @@ npm i -g coding-friend-cli
16
16
  ## Commands
17
17
 
18
18
  ```bash
19
- cf install # Install the Coding Friend plugin into Claude Code
20
- # 💡 Safe to run multiple times (idempotent).
21
- cf uninstall # Completely remove plugin, marketplace, statusline, completion
22
- # 💡 Interactive asks for confirmation before acting.
19
+ cf install # Install plugin (interactive scope chooser)
20
+ cf install --user # Install at user scope (all projects)
21
+ cf install --global # Same as --user
22
+ cf install --project # Install at project scope (shared via git)
23
+ cf install --local # Install at local scope (this machine only)
24
+ # 💡 Safe to run multiple times (idempotent).
25
+ cf uninstall # Uninstall plugin (interactive scope chooser)
26
+ cf uninstall --user # Uninstall from user scope (full cleanup)
27
+ cf uninstall --global # Same as --user
28
+ cf uninstall --project # Uninstall from project scope only
29
+ cf uninstall --local # Uninstall from local scope only
30
+ # 💡 Interactive — asks for confirmation before acting.
31
+ cf disable # Disable plugin (interactive scope chooser)
32
+ cf disable --user # Disable at user scope (all projects)
33
+ cf disable --global # Same as --user
34
+ cf disable --project # Disable at project scope
35
+ cf disable --local # Disable at local scope
36
+ # 💡 Plugin stays installed but won't load.
37
+ cf enable # Re-enable plugin (interactive scope chooser)
38
+ cf enable --user # Enable at user scope (all projects)
39
+ cf enable --global # Same as --user
40
+ cf enable --project # Enable at project scope
41
+ cf enable --local # Enable at local scope
23
42
  cf init # Initialize workspace (interactive)
24
43
  # 💡 You can run this anywhere, anytime.
25
44
  cf config # Manage Coding Friend configuration (interactive menu)
@@ -32,10 +51,12 @@ cf mcp [path] # Setup MCP server for LLM integration
32
51
  cf permission # Manage Claude Code permission rules for Coding Friend
33
52
  cf permission --all # Apply all recommended permissions without prompts
34
53
  cf statusline # Setup coding-friend statusline
35
- cf update # Update plugin + CLI + statusline
36
- cf update --cli # Update only the CLI (npm package)
37
- cf update --plugin # Update only the Claude Code plugin
54
+ cf update # Update plugin + CLI + statusline
55
+ cf update --cli # Update only the CLI (npm package)
56
+ cf update --plugin # Update only the Claude Code plugin
38
57
  cf update --statusline # Update only the statusline
58
+ cf update --project # Update plugin at project scope
59
+ cf update --local # Update plugin at local scope
39
60
  cf dev on [path] # Switch to local plugin source for development
40
61
  cf dev off # Switch back to remote marketplace
41
62
  cf dev status # Show current dev mode (local or remote)
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readJson,
3
3
  writeJson
4
- } from "./chunk-HQ5BPWEV.js";
4
+ } from "./chunk-RWUTFVRB.js";
5
5
 
6
6
  // src/lib/permissions.ts
7
7
  var PERMISSION_RULES = [
@@ -5,7 +5,7 @@ import {
5
5
  globalConfigPath,
6
6
  localConfigPath,
7
7
  readJson
8
- } from "./chunk-HQ5BPWEV.js";
8
+ } from "./chunk-RWUTFVRB.js";
9
9
  import {
10
10
  log
11
11
  } from "./chunk-W5CD7WTX.js";
@@ -33,6 +33,51 @@ async function askScope(label = "Save to:") {
33
33
  ]
34
34
  });
35
35
  }
36
+ async function askPluginScope(message = "Where should the plugin be installed?") {
37
+ return select({
38
+ message,
39
+ choices: [
40
+ {
41
+ name: "User / Global \u2014 available in all projects",
42
+ value: "user"
43
+ },
44
+ {
45
+ name: "Project \u2014 shared with team via <project>.claude/settings.json",
46
+ value: "project"
47
+ },
48
+ {
49
+ name: "Local \u2014 this machine only via <project>.claude/settings.local.json, not shared (gitignored)",
50
+ value: "local"
51
+ }
52
+ ]
53
+ });
54
+ }
55
+ async function resolveScope(opts, message) {
56
+ const flags = [
57
+ { key: "user", scope: "user" },
58
+ { key: "global", scope: "user" },
59
+ { key: "project", scope: "project" },
60
+ { key: "local", scope: "local" }
61
+ ];
62
+ const active = flags.filter((f) => opts[f.key] === true);
63
+ const uniqueScopes = new Set(active.map((f) => f.scope));
64
+ if (uniqueScopes.size > 1) {
65
+ log.error(
66
+ "Only one scope flag can be used at a time: --user, --global, --project, or --local"
67
+ );
68
+ process.exit(1);
69
+ }
70
+ if (uniqueScopes.size === 1) {
71
+ return active[0].scope;
72
+ }
73
+ if (!process.stdin.isTTY) {
74
+ log.error(
75
+ "No scope flag provided and stdin is not interactive. Use --user (or --global), --project, or --local."
76
+ );
77
+ process.exit(1);
78
+ }
79
+ return askPluginScope(message);
80
+ }
36
81
  function showConfigHint() {
37
82
  console.log(chalk.dim("Config files:"));
38
83
  console.log(chalk.dim(" Global: ~/.coding-friend/config.json"));
@@ -127,6 +172,7 @@ export {
127
172
  BACK,
128
173
  injectBackChoice,
129
174
  askScope,
175
+ resolveScope,
130
176
  showConfigHint,
131
177
  getScopeLabel,
132
178
  formatScopeLabel,
@@ -6,7 +6,7 @@ import {
6
6
  localConfigPath,
7
7
  readJson,
8
8
  resolvePath
9
- } from "./chunk-HQ5BPWEV.js";
9
+ } from "./chunk-RWUTFVRB.js";
10
10
 
11
11
  // src/lib/config.ts
12
12
  function loadConfig() {
@@ -21,7 +21,9 @@ ${MARKER_START}
21
21
  _cf_completions() {
22
22
  local cur="\${COMP_WORDS[COMP_CWORD]}"
23
23
  local prev="\${COMP_WORDS[COMP_CWORD-1]}"
24
- local commands="install uninstall init config host mcp permission statusline update dev session"
24
+ local commands="install uninstall disable enable init config host mcp permission statusline update dev session"
25
+ local scope_flags="--user --global --project --local"
26
+ local update_flags="--cli --plugin --statusline --user --global --project --local"
25
27
 
26
28
  # Subcommands for 'dev'
27
29
  if [[ "\${COMP_WORDS[1]}" == "dev" && \${COMP_CWORD} -eq 2 ]]; then
@@ -41,6 +43,18 @@ _cf_completions() {
41
43
  return
42
44
  fi
43
45
 
46
+ # Flag completion for install/uninstall/disable/enable
47
+ if [[ "\${COMP_WORDS[1]}" == "install" || "\${COMP_WORDS[1]}" == "uninstall" || "\${COMP_WORDS[1]}" == "disable" || "\${COMP_WORDS[1]}" == "enable" ]] && [[ "$cur" == -* ]]; then
48
+ COMPREPLY=($(compgen -W "$scope_flags" -- "$cur"))
49
+ return
50
+ fi
51
+
52
+ # Flag completion for update
53
+ if [[ "\${COMP_WORDS[1]}" == "update" && "$cur" == -* ]]; then
54
+ COMPREPLY=($(compgen -W "$update_flags" -- "$cur"))
55
+ return
56
+ fi
57
+
44
58
  COMPREPLY=($(compgen -W "$commands" -- "$cur"))
45
59
  }
46
60
  complete -o default -F _cf_completions cf
@@ -51,6 +65,8 @@ var ZSH_FUNCTION_BODY = `_cf() {
51
65
  commands=(
52
66
  'install:Install the Coding Friend plugin into Claude Code'
53
67
  'uninstall:Uninstall the Coding Friend plugin from Claude Code'
68
+ 'disable:Disable the Coding Friend plugin without uninstalling'
69
+ 'enable:Re-enable the Coding Friend plugin'
54
70
  'init:Initialize coding-friend in current project'
55
71
  'config:Manage Coding Friend configuration'
56
72
  'host:Build and serve learning docs as a static website'
@@ -62,8 +78,31 @@ var ZSH_FUNCTION_BODY = `_cf() {
62
78
  'session:Save and load Claude Code sessions across machines'
63
79
  )
64
80
 
81
+ local -a scope_flags
82
+ scope_flags=(
83
+ '--user[Install at user scope (all projects)]'
84
+ '--global[Install at user scope (all projects)]'
85
+ '--project[Install at project scope (shared via git)]'
86
+ '--local[Install at local scope (this machine only)]'
87
+ )
88
+
89
+ local -a update_flags
90
+ update_flags=(
91
+ '--cli[Update only the CLI (npm package)]'
92
+ '--plugin[Update only the Claude Code plugin]'
93
+ '--statusline[Update only the statusline]'
94
+ '--user[Update plugin at user scope (all projects)]'
95
+ '--global[Update plugin at user scope (all projects)]'
96
+ '--project[Update plugin at project scope]'
97
+ '--local[Update plugin at local scope]'
98
+ )
99
+
65
100
  if (( CURRENT == 2 )); then
66
101
  _describe 'command' commands
102
+ elif (( CURRENT >= 3 )) && [[ "\${words[2]}" == "install" || "\${words[2]}" == "uninstall" || "\${words[2]}" == "disable" || "\${words[2]}" == "enable" ]]; then
103
+ _values 'flags' $scope_flags
104
+ elif (( CURRENT >= 3 )) && [[ "\${words[2]}" == "update" ]]; then
105
+ _values 'flags' $update_flags
67
106
  elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "dev" ]]; then
68
107
  local -a subcommands
69
108
  subcommands=(
@@ -100,6 +139,8 @@ var FISH_CONTENT = `# coding-friend CLI completions
100
139
  complete -c cf -f
101
140
  complete -c cf -n "__fish_use_subcommand" -a install -d "Install the Coding Friend plugin into Claude Code"
102
141
  complete -c cf -n "__fish_use_subcommand" -a uninstall -d "Uninstall the Coding Friend plugin from Claude Code"
142
+ complete -c cf -n "__fish_use_subcommand" -a disable -d "Disable the Coding Friend plugin without uninstalling"
143
+ complete -c cf -n "__fish_use_subcommand" -a enable -d "Re-enable the Coding Friend plugin"
103
144
  complete -c cf -n "__fish_use_subcommand" -a init -d "Initialize coding-friend in current project"
104
145
  complete -c cf -n "__fish_use_subcommand" -a config -d "Manage Coding Friend configuration"
105
146
  complete -c cf -n "__fish_use_subcommand" -a host -d "Build and serve learning docs as a static website"
@@ -109,12 +150,27 @@ complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend
109
150
  complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
110
151
  complete -c cf -n "__fish_use_subcommand" -a dev -d "Switch between local and remote plugin for development"
111
152
  complete -c cf -n "__fish_use_subcommand" -a session -d "Save and load Claude Code sessions across machines"
153
+ # Scope flags for install/uninstall/disable/enable
154
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall disable enable" -l user -d "User scope (all projects)"
155
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall disable enable" -l global -d "User scope (all projects)"
156
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall disable enable" -l project -d "Project scope (shared via git)"
157
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall disable enable" -l local -d "Local scope (this machine only)"
158
+ # Flags for update
159
+ complete -c cf -n "__fish_seen_subcommand_from update" -l cli -d "Update only the CLI"
160
+ complete -c cf -n "__fish_seen_subcommand_from update" -l plugin -d "Update only the plugin"
161
+ complete -c cf -n "__fish_seen_subcommand_from update" -l statusline -d "Update only the statusline"
162
+ complete -c cf -n "__fish_seen_subcommand_from update" -l user -d "User scope (all projects)"
163
+ complete -c cf -n "__fish_seen_subcommand_from update" -l global -d "User scope (all projects)"
164
+ complete -c cf -n "__fish_seen_subcommand_from update" -l project -d "Project scope"
165
+ complete -c cf -n "__fish_seen_subcommand_from update" -l local -d "Local scope"
166
+ # Dev subcommands
112
167
  complete -c cf -n "__fish_seen_subcommand_from dev" -a on -d "Switch to local plugin source"
113
168
  complete -c cf -n "__fish_seen_subcommand_from dev" -a off -d "Switch back to remote marketplace"
114
169
  complete -c cf -n "__fish_seen_subcommand_from dev" -a status -d "Show current dev mode"
115
170
  complete -c cf -n "__fish_seen_subcommand_from dev" -a restart -d "Restart dev mode"
116
171
  complete -c cf -n "__fish_seen_subcommand_from dev" -a sync -d "Sync local plugin files"
117
172
  complete -c cf -n "__fish_seen_subcommand_from dev" -a update -d "Update local dev plugin"
173
+ # Session subcommands
118
174
  complete -c cf -n "__fish_seen_subcommand_from session" -a save -d "Save current session to docs/sessions/"
119
175
  complete -c cf -n "__fish_seen_subcommand_from session" -a load -d "Load a saved session from docs/sessions/"
120
176
  `;
@@ -123,9 +179,11 @@ var POWERSHELL_BLOCK = `
123
179
  ${MARKER_START}
124
180
  Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
125
181
  param($wordToComplete, $commandAst, $cursorPosition)
126
- $commands = @('install','uninstall','init','config','host','mcp','permission','statusline','update','dev','session')
182
+ $commands = @('install','uninstall','disable','enable','init','config','host','mcp','permission','statusline','update','dev','session')
127
183
  $devSubcommands = @('on','off','status','restart','sync','update')
128
184
  $sessionSubcommands = @('save','load')
185
+ $scopeFlags = @('--user','--global','--project','--local')
186
+ $updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
129
187
  $words = $commandAst.CommandElements
130
188
  if ($words.Count -ge 2 -and $words[1].ToString() -eq 'dev') {
131
189
  $devSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
@@ -133,6 +191,12 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
133
191
  } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'session') {
134
192
  $sessionSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
135
193
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
194
+ } elseif ($words.Count -ge 2 -and ($words[1].ToString() -eq 'install' -or $words[1].ToString() -eq 'uninstall' -or $words[1].ToString() -eq 'disable' -or $words[1].ToString() -eq 'enable')) {
195
+ $scopeFlags | Where-Object { $_ -like "$wordToComplete*" } |
196
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
197
+ } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'update') {
198
+ $updateFlags | Where-Object { $_ -like "$wordToComplete*" } |
199
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
136
200
  } else {
137
201
  $commands | Where-Object { $_ -like "$wordToComplete*" } |
138
202
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
@@ -10,7 +10,7 @@ import {
10
10
  pluginCachePath,
11
11
  readJson,
12
12
  writeJson
13
- } from "./chunk-HQ5BPWEV.js";
13
+ } from "./chunk-RWUTFVRB.js";
14
14
 
15
15
  // src/lib/statusline.ts
16
16
  import { existsSync, readdirSync } from "fs";
@@ -1,26 +1,3 @@
1
- // src/lib/json.ts
2
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3
- import { dirname } from "path";
4
- function readJson(filePath) {
5
- try {
6
- const content = readFileSync(filePath, "utf-8");
7
- return JSON.parse(content);
8
- } catch {
9
- return null;
10
- }
11
- }
12
- function writeJson(filePath, data) {
13
- const dir = dirname(filePath);
14
- if (!existsSync(dir)) {
15
- mkdirSync(dir, { recursive: true });
16
- }
17
- writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
18
- }
19
- function mergeJson(filePath, data) {
20
- const existing = readJson(filePath) ?? {};
21
- writeJson(filePath, { ...existing, ...data });
22
- }
23
-
24
1
  // src/lib/paths.ts
25
2
  import { homedir } from "os";
26
3
  import { resolve, join } from "path";
@@ -38,6 +15,9 @@ function globalConfigPath() {
38
15
  function claudeSettingsPath() {
39
16
  return join(homedir(), ".claude", "settings.json");
40
17
  }
18
+ function claudeProjectSettingsPath() {
19
+ return resolve(process.cwd(), ".claude", "settings.json");
20
+ }
41
21
  function claudeLocalSettingsPath() {
42
22
  return resolve(process.cwd(), ".claude", "settings.local.json");
43
23
  }
@@ -91,14 +71,35 @@ function claudeSessionDir(encodedPath) {
91
71
  return join(claudeProjectsDir(), encodedPath);
92
72
  }
93
73
 
74
+ // src/lib/json.ts
75
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
76
+ import { dirname } from "path";
77
+ function readJson(filePath) {
78
+ try {
79
+ const content = readFileSync(filePath, "utf-8");
80
+ return JSON.parse(content);
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function writeJson(filePath, data) {
86
+ const dir = dirname(filePath);
87
+ if (!existsSync(dir)) {
88
+ mkdirSync(dir, { recursive: true });
89
+ }
90
+ writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
91
+ }
92
+ function mergeJson(filePath, data) {
93
+ const existing = readJson(filePath) ?? {};
94
+ writeJson(filePath, { ...existing, ...data });
95
+ }
96
+
94
97
  export {
95
- readJson,
96
- writeJson,
97
- mergeJson,
98
98
  resolvePath,
99
99
  localConfigPath,
100
100
  globalConfigPath,
101
101
  claudeSettingsPath,
102
+ claudeProjectSettingsPath,
102
103
  claudeLocalSettingsPath,
103
104
  installedPluginsPath,
104
105
  pluginCachePath,
@@ -108,5 +109,8 @@ export {
108
109
  marketplaceClonePath,
109
110
  globalConfigDir,
110
111
  encodeProjectPath,
111
- claudeSessionDir
112
+ claudeSessionDir,
113
+ readJson,
114
+ writeJson,
115
+ mergeJson
112
116
  };
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  ensureStatusline,
3
3
  getInstalledVersion
4
- } from "./chunk-YAOUAJZP.js";
4
+ } from "./chunk-QG6XYVJU.js";
5
5
  import {
6
6
  ensureShellCompletion
7
- } from "./chunk-BCYBFGVD.js";
7
+ } from "./chunk-KJUGTLPQ.js";
8
+ import {
9
+ resolveScope
10
+ } from "./chunk-EMAINEYB.js";
8
11
  import {
9
12
  commandExists,
10
13
  run,
@@ -13,7 +16,7 @@ import {
13
16
  import {
14
17
  claudeSettingsPath,
15
18
  readJson
16
- } from "./chunk-HQ5BPWEV.js";
19
+ } from "./chunk-RWUTFVRB.js";
17
20
  import {
18
21
  log
19
22
  } from "./chunk-W5CD7WTX.js";
@@ -86,6 +89,13 @@ async function updateCommand(opts) {
86
89
  const doCli = updateAll || !!opts.cli;
87
90
  const doPlugin = updateAll || !!opts.plugin;
88
91
  const doStatusline = updateAll || !!opts.statusline;
92
+ const hasScopeFlag = !!(opts.user || opts.global || opts.project || opts.local);
93
+ let scope;
94
+ if (doPlugin && hasScopeFlag) {
95
+ scope = await resolveScope(opts);
96
+ } else if (doPlugin && !hasScopeFlag) {
97
+ scope = "user";
98
+ }
89
99
  console.log("=== \u{1F33F} Coding Friend Update \u{1F33F} ===");
90
100
  console.log();
91
101
  const currentVersion = getInstalledVersion();
@@ -135,12 +145,18 @@ async function updateCommand(opts) {
135
145
  "Claude CLI not found. Install it first, or run: claude plugin update coding-friend@coding-friend-marketplace"
136
146
  );
137
147
  } else {
138
- log.step("Updating plugin...");
139
- const result = run("claude", [
148
+ log.step(
149
+ `Updating plugin${scope && scope !== "user" ? ` (${scope} scope)` : ""}...`
150
+ );
151
+ const updateArgs = [
140
152
  "plugin",
141
153
  "update",
142
154
  "coding-friend@coding-friend-marketplace"
143
- ]);
155
+ ];
156
+ if (scope) {
157
+ updateArgs.push("--scope", scope);
158
+ }
159
+ const result = run("claude", updateArgs);
144
160
  if (result === null) {
145
161
  log.error(
146
162
  "Plugin update failed. Try manually: claude plugin update coding-friend@coding-friend-marketplace"
@@ -0,0 +1,68 @@
1
+ import {
2
+ claudeLocalSettingsPath,
3
+ claudeProjectSettingsPath,
4
+ claudeSettingsPath,
5
+ installedPluginsPath,
6
+ knownMarketplacesPath,
7
+ readJson,
8
+ writeJson
9
+ } from "./chunk-RWUTFVRB.js";
10
+
11
+ // src/lib/plugin-state.ts
12
+ var MARKETPLACE_NAME = "coding-friend-marketplace";
13
+ var PLUGIN_NAME = "coding-friend";
14
+ var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
15
+ function isPluginInstalled() {
16
+ const data = readJson(installedPluginsPath());
17
+ if (!data) return false;
18
+ const plugins = data.plugins ?? data;
19
+ return Object.keys(plugins).some((k) => k.includes(PLUGIN_NAME));
20
+ }
21
+ function isMarketplaceRegistered() {
22
+ const data = readJson(knownMarketplacesPath());
23
+ if (!data) return false;
24
+ return MARKETPLACE_NAME in data;
25
+ }
26
+ function settingsPathForScope(scope) {
27
+ switch (scope) {
28
+ case "user":
29
+ return claudeSettingsPath();
30
+ case "project":
31
+ return claudeProjectSettingsPath();
32
+ case "local":
33
+ return claudeLocalSettingsPath();
34
+ }
35
+ }
36
+ function isPluginDisabled(scope) {
37
+ const settings = readJson(
38
+ settingsPathForScope(scope)
39
+ );
40
+ if (!settings) return false;
41
+ const enabled = settings.enabledPlugins;
42
+ if (!enabled) return false;
43
+ return enabled[PLUGIN_ID] === false;
44
+ }
45
+ function setPluginEnabled(scope, enabled) {
46
+ const filePath = settingsPathForScope(scope);
47
+ const settings = readJson(filePath) ?? {};
48
+ const enabledPlugins = settings.enabledPlugins ?? {};
49
+ if (enabled) {
50
+ delete enabledPlugins[PLUGIN_ID];
51
+ if (Object.keys(enabledPlugins).length === 0) {
52
+ delete settings.enabledPlugins;
53
+ } else {
54
+ settings.enabledPlugins = enabledPlugins;
55
+ }
56
+ } else {
57
+ enabledPlugins[PLUGIN_ID] = false;
58
+ settings.enabledPlugins = enabledPlugins;
59
+ }
60
+ writeJson(filePath, settings);
61
+ }
62
+
63
+ export {
64
+ isPluginInstalled,
65
+ isMarketplaceRegistered,
66
+ isPluginDisabled,
67
+ setPluginEnabled
68
+ };
@@ -1,29 +1,29 @@
1
- import {
2
- BACK,
3
- applyDocsDirChange,
4
- askScope,
5
- formatScopeLabel,
6
- getMergedValue,
7
- getScopeLabel,
8
- injectBackChoice,
9
- showConfigHint
10
- } from "./chunk-AHGBESGW.js";
11
1
  import {
12
2
  findStatuslineHookPath,
13
3
  isStatuslineConfigured,
14
4
  saveStatuslineConfig,
15
5
  selectStatuslineComponents,
16
6
  writeStatuslineSettings
17
- } from "./chunk-YAOUAJZP.js";
7
+ } from "./chunk-QG6XYVJU.js";
8
+ import {
9
+ ALL_COMPONENT_IDS,
10
+ DEFAULT_CONFIG
11
+ } from "./chunk-PGLUEN7D.js";
18
12
  import {
19
13
  ensureShellCompletion,
20
14
  hasShellCompletion,
21
15
  removeShellCompletion
22
- } from "./chunk-BCYBFGVD.js";
16
+ } from "./chunk-KJUGTLPQ.js";
23
17
  import {
24
- ALL_COMPONENT_IDS,
25
- DEFAULT_CONFIG
26
- } from "./chunk-PGLUEN7D.js";
18
+ BACK,
19
+ applyDocsDirChange,
20
+ askScope,
21
+ formatScopeLabel,
22
+ getMergedValue,
23
+ getScopeLabel,
24
+ injectBackChoice,
25
+ showConfigHint
26
+ } from "./chunk-EMAINEYB.js";
27
27
  import {
28
28
  run
29
29
  } from "./chunk-UFGNO6CW.js";
@@ -33,7 +33,7 @@ import {
33
33
  mergeJson,
34
34
  readJson,
35
35
  resolvePath
36
- } from "./chunk-HQ5BPWEV.js";
36
+ } from "./chunk-RWUTFVRB.js";
37
37
  import {
38
38
  log
39
39
  } from "./chunk-W5CD7WTX.js";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  isMarketplaceRegistered,
3
3
  isPluginInstalled
4
- } from "./chunk-TLLWOWYU.js";
4
+ } from "./chunk-ZOOFPF5I.js";
5
5
  import {
6
6
  ensureStatusline
7
- } from "./chunk-YAOUAJZP.js";
7
+ } from "./chunk-QG6XYVJU.js";
8
+ import "./chunk-PGLUEN7D.js";
8
9
  import {
9
10
  ensureShellCompletion
10
- } from "./chunk-BCYBFGVD.js";
11
- import "./chunk-PGLUEN7D.js";
11
+ } from "./chunk-KJUGTLPQ.js";
12
12
  import {
13
13
  commandExists,
14
14
  run
@@ -19,7 +19,7 @@ import {
19
19
  pluginCachePath,
20
20
  readJson,
21
21
  writeJson
22
- } from "./chunk-HQ5BPWEV.js";
22
+ } from "./chunk-RWUTFVRB.js";
23
23
  import {
24
24
  log
25
25
  } from "./chunk-W5CD7WTX.js";
@@ -0,0 +1,35 @@
1
+ import {
2
+ isPluginDisabled,
3
+ setPluginEnabled
4
+ } from "./chunk-ZOOFPF5I.js";
5
+ import {
6
+ resolveScope
7
+ } from "./chunk-EMAINEYB.js";
8
+ import "./chunk-UFGNO6CW.js";
9
+ import "./chunk-RWUTFVRB.js";
10
+ import {
11
+ log
12
+ } from "./chunk-W5CD7WTX.js";
13
+
14
+ // src/commands/disable.ts
15
+ import chalk from "chalk";
16
+ async function disableCommand(opts = {}) {
17
+ const scope = await resolveScope(
18
+ opts,
19
+ "Where should Coding Friend be disabled?"
20
+ );
21
+ if (isPluginDisabled(scope)) {
22
+ log.info(
23
+ `Coding Friend is already disabled at ${chalk.cyan(scope)} scope.`
24
+ );
25
+ return;
26
+ }
27
+ log.step(`Disabling plugin (${chalk.cyan(scope)} scope)...`);
28
+ setPluginEnabled(scope, false);
29
+ log.success(`Coding Friend disabled at ${chalk.cyan(scope)} scope.`);
30
+ log.dim("Restart Claude Code for the change to take effect.");
31
+ log.dim(`Run ${chalk.bold(`cf enable --${scope}`)} to re-enable.`);
32
+ }
33
+ export {
34
+ disableCommand
35
+ };
@@ -0,0 +1,32 @@
1
+ import {
2
+ isPluginDisabled,
3
+ setPluginEnabled
4
+ } from "./chunk-ZOOFPF5I.js";
5
+ import {
6
+ resolveScope
7
+ } from "./chunk-EMAINEYB.js";
8
+ import "./chunk-UFGNO6CW.js";
9
+ import "./chunk-RWUTFVRB.js";
10
+ import {
11
+ log
12
+ } from "./chunk-W5CD7WTX.js";
13
+
14
+ // src/commands/enable.ts
15
+ import chalk from "chalk";
16
+ async function enableCommand(opts = {}) {
17
+ const scope = await resolveScope(
18
+ opts,
19
+ "Where should Coding Friend be enabled?"
20
+ );
21
+ if (!isPluginDisabled(scope)) {
22
+ log.info(`Coding Friend is already enabled at ${chalk.cyan(scope)} scope.`);
23
+ return;
24
+ }
25
+ log.step(`Enabling plugin (${chalk.cyan(scope)} scope)...`);
26
+ setPluginEnabled(scope, true);
27
+ log.success(`Coding Friend enabled at ${chalk.cyan(scope)} scope.`);
28
+ log.dim("Restart Claude Code for the change to take effect.");
29
+ }
30
+ export {
31
+ enableCommand
32
+ };
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-RZRT7NGT.js";
4
4
  import {
5
5
  resolveDocsDir
6
- } from "./chunk-EDVZPZ3B.js";
6
+ } from "./chunk-KB4JM2EB.js";
7
7
  import "./chunk-PGLUEN7D.js";
8
8
  import {
9
9
  run,
10
10
  streamExec
11
11
  } from "./chunk-UFGNO6CW.js";
12
- import "./chunk-HQ5BPWEV.js";
12
+ import "./chunk-RWUTFVRB.js";
13
13
  import {
14
14
  log
15
15
  } from "./chunk-W5CD7WTX.js";
package/dist/index.js CHANGED
@@ -13,40 +13,48 @@ var program = new Command();
13
13
  program.name("cf").description(
14
14
  "coding-friend CLI \u2014 host learning docs, setup MCP, init projects"
15
15
  ).version(pkg.version, "-v, --version");
16
- program.command("install").description("Install the Coding Friend plugin into Claude Code").action(async () => {
17
- const { installCommand } = await import("./install-YEAWW7ZX.js");
18
- await installCommand();
16
+ program.command("install").description("Install the Coding Friend plugin into Claude Code").option("--user", "Install at user scope (all projects)").option("--global", "Install at user scope (all projects)").option("--project", "Install at project scope (shared via git)").option("--local", "Install at local scope (this machine only)").action(async (opts) => {
17
+ const { installCommand } = await import("./install-FEOOFN4G.js");
18
+ await installCommand(opts);
19
19
  });
20
- program.command("uninstall").description("Uninstall the Coding Friend plugin from Claude Code").action(async () => {
21
- const { uninstallCommand } = await import("./uninstall-3ISPGQHE.js");
22
- await uninstallCommand();
20
+ program.command("uninstall").description("Uninstall the Coding Friend plugin from Claude Code").option("--user", "Uninstall from user scope (all projects)").option("--global", "Uninstall from user scope (all projects)").option("--project", "Uninstall from project scope").option("--local", "Uninstall from local scope").action(async (opts) => {
21
+ const { uninstallCommand } = await import("./uninstall-2SYDCOAP.js");
22
+ await uninstallCommand(opts);
23
+ });
24
+ program.command("disable").description("Disable the Coding Friend plugin without uninstalling").option("--user", "Disable at user scope (all projects)").option("--global", "Disable at user scope (all projects)").option("--project", "Disable at project scope").option("--local", "Disable at local scope").action(async (opts) => {
25
+ const { disableCommand } = await import("./disable-3N2NCMTU.js");
26
+ await disableCommand(opts);
27
+ });
28
+ program.command("enable").description("Re-enable the Coding Friend plugin").option("--user", "Enable at user scope (all projects)").option("--global", "Enable at user scope (all projects)").option("--project", "Enable at project scope").option("--local", "Enable at local scope").action(async (opts) => {
29
+ const { enableCommand } = await import("./enable-EGG6LTXD.js");
30
+ await enableCommand(opts);
23
31
  });
24
32
  program.command("init").description("Initialize coding-friend in current project").action(async () => {
25
- const { initCommand } = await import("./init-BG2ESQQC.js");
33
+ const { initCommand } = await import("./init-2LMIUWUY.js");
26
34
  await initCommand();
27
35
  });
28
36
  program.command("config").description("Manage Coding Friend configuration").action(async () => {
29
- const { configCommand } = await import("./config-6P3SE3NS.js");
37
+ const { configCommand } = await import("./config-PTMK6XBN.js");
30
38
  await configCommand();
31
39
  });
32
40
  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) => {
33
- const { hostCommand } = await import("./host-BPO2STZA.js");
41
+ const { hostCommand } = await import("./host-XHBUWGAW.js");
34
42
  await hostCommand(path, opts);
35
43
  });
36
44
  program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
37
- const { mcpCommand } = await import("./mcp-54UB5MF2.js");
45
+ const { mcpCommand } = await import("./mcp-XE6NZRPD.js");
38
46
  await mcpCommand(path);
39
47
  });
40
48
  program.command("permission").description("Manage Claude Code permission rules for Coding Friend").option("--all", "Apply all recommended permissions without prompts").action(async (opts) => {
41
- const { permissionCommand } = await import("./permission-GPOUJUZS.js");
49
+ const { permissionCommand } = await import("./permission-L2QQR5PO.js");
42
50
  await permissionCommand(opts);
43
51
  });
44
52
  program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
45
- const { statuslineCommand } = await import("./statusline-L2CYEV4H.js");
53
+ const { statuslineCommand } = await import("./statusline-5ZMM3RYY.js");
46
54
  await statuslineCommand();
47
55
  });
48
- 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) => {
49
- const { updateCommand } = await import("./update-EN6S3FKT.js");
56
+ 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").option("--user", "Update plugin at user scope (all projects)").option("--global", "Update plugin at user scope (all projects)").option("--project", "Update plugin at project scope").option("--local", "Update plugin at local scope").action(async (opts) => {
57
+ const { updateCommand } = await import("./update-4MNVBTI5.js");
50
58
  await updateCommand(opts);
51
59
  });
52
60
  var session = program.command("session").description("Save and load Claude Code sessions across machines");
@@ -61,11 +69,11 @@ session.command("save").description("Save current Claude Code session to sync fo
61
69
  "-s, --session-id <id>",
62
70
  "session UUID to save (default: auto-detect newest)"
63
71
  ).option("-l, --label <label>", "label for this session").action(async (opts) => {
64
- const { sessionSaveCommand } = await import("./session-STYOO4A4.js");
72
+ const { sessionSaveCommand } = await import("./session-K6YWJLLU.js");
65
73
  await sessionSaveCommand(opts);
66
74
  });
67
75
  session.command("load").description("Load a saved session from sync folder").action(async () => {
68
- const { sessionLoadCommand } = await import("./session-STYOO4A4.js");
76
+ const { sessionLoadCommand } = await import("./session-K6YWJLLU.js");
69
77
  await sessionLoadCommand();
70
78
  });
71
79
  var dev = program.command("dev").description("Development mode commands");
@@ -81,35 +89,35 @@ Dev subcommands:
81
89
  dev update [path] Update local dev plugin to latest version`
82
90
  );
83
91
  dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
84
- const { devOnCommand } = await import("./dev-4NLCK6YG.js");
92
+ const { devOnCommand } = await import("./dev-UBYW6NRX.js");
85
93
  await devOnCommand(path);
86
94
  });
87
95
  dev.command("off").description("Switch back to remote marketplace").action(async () => {
88
- const { devOffCommand } = await import("./dev-4NLCK6YG.js");
96
+ const { devOffCommand } = await import("./dev-UBYW6NRX.js");
89
97
  await devOffCommand();
90
98
  });
91
99
  dev.command("status").description("Show current dev mode").action(async () => {
92
- const { devStatusCommand } = await import("./dev-4NLCK6YG.js");
100
+ const { devStatusCommand } = await import("./dev-UBYW6NRX.js");
93
101
  await devStatusCommand();
94
102
  });
95
103
  dev.command("sync").description(
96
104
  "Copy local source files to plugin cache (no version bump needed)"
97
105
  ).action(async () => {
98
- const { devSyncCommand } = await import("./dev-4NLCK6YG.js");
106
+ const { devSyncCommand } = await import("./dev-UBYW6NRX.js");
99
107
  await devSyncCommand();
100
108
  });
101
109
  dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
102
110
  "[path]",
103
111
  "path to local coding-friend repo (default: saved path or cwd)"
104
112
  ).action(async (path) => {
105
- const { devRestartCommand } = await import("./dev-4NLCK6YG.js");
113
+ const { devRestartCommand } = await import("./dev-UBYW6NRX.js");
106
114
  await devRestartCommand(path);
107
115
  });
108
116
  dev.command("update").description("Update local dev plugin to latest version (off + on)").argument(
109
117
  "[path]",
110
118
  "path to local coding-friend repo (default: saved path or cwd)"
111
119
  ).action(async (path) => {
112
- const { devUpdateCommand } = await import("./dev-4NLCK6YG.js");
120
+ const { devUpdateCommand } = await import("./dev-UBYW6NRX.js");
113
121
  await devUpdateCommand(path);
114
122
  });
115
123
  program.parse();
@@ -3,31 +3,31 @@ import {
3
3
  buildLearnDirRules,
4
4
  getExistingRules,
5
5
  getMissingRules
6
- } from "./chunk-4C4EKYM3.js";
7
- import {
8
- BACK,
9
- applyDocsDirChange,
10
- askScope,
11
- formatScopeLabel,
12
- getMergedValue,
13
- getScopeLabel,
14
- injectBackChoice,
15
- showConfigHint
16
- } from "./chunk-AHGBESGW.js";
6
+ } from "./chunk-56U7US6J.js";
17
7
  import {
18
8
  findStatuslineHookPath,
19
9
  isStatuslineConfigured,
20
10
  saveStatuslineConfig,
21
11
  selectStatuslineComponents,
22
12
  writeStatuslineSettings
23
- } from "./chunk-YAOUAJZP.js";
13
+ } from "./chunk-QG6XYVJU.js";
14
+ import {
15
+ DEFAULT_CONFIG
16
+ } from "./chunk-PGLUEN7D.js";
24
17
  import {
25
18
  ensureShellCompletion,
26
19
  hasShellCompletion
27
- } from "./chunk-BCYBFGVD.js";
20
+ } from "./chunk-KJUGTLPQ.js";
28
21
  import {
29
- DEFAULT_CONFIG
30
- } from "./chunk-PGLUEN7D.js";
22
+ BACK,
23
+ applyDocsDirChange,
24
+ askScope,
25
+ formatScopeLabel,
26
+ getMergedValue,
27
+ getScopeLabel,
28
+ injectBackChoice,
29
+ showConfigHint
30
+ } from "./chunk-EMAINEYB.js";
31
31
  import {
32
32
  run
33
33
  } from "./chunk-UFGNO6CW.js";
@@ -38,7 +38,7 @@ import {
38
38
  mergeJson,
39
39
  readJson,
40
40
  resolvePath
41
- } from "./chunk-HQ5BPWEV.js";
41
+ } from "./chunk-RWUTFVRB.js";
42
42
  import {
43
43
  log
44
44
  } from "./chunk-W5CD7WTX.js";
@@ -1,27 +1,36 @@
1
1
  import {
2
2
  getLatestVersion,
3
3
  semverCompare
4
- } from "./chunk-UR5XUEH5.js";
4
+ } from "./chunk-YZ7IZ46F.js";
5
5
  import {
6
- isMarketplaceRegistered
7
- } from "./chunk-TLLWOWYU.js";
6
+ isMarketplaceRegistered,
7
+ isPluginDisabled
8
+ } from "./chunk-ZOOFPF5I.js";
8
9
  import {
9
10
  getInstalledVersion
10
- } from "./chunk-YAOUAJZP.js";
11
- import "./chunk-BCYBFGVD.js";
11
+ } from "./chunk-QG6XYVJU.js";
12
12
  import "./chunk-PGLUEN7D.js";
13
+ import {
14
+ ensureShellCompletion
15
+ } from "./chunk-KJUGTLPQ.js";
16
+ import {
17
+ resolveScope
18
+ } from "./chunk-EMAINEYB.js";
13
19
  import {
14
20
  commandExists,
15
21
  run
16
22
  } from "./chunk-UFGNO6CW.js";
17
- import "./chunk-HQ5BPWEV.js";
23
+ import {
24
+ devStatePath
25
+ } from "./chunk-RWUTFVRB.js";
18
26
  import {
19
27
  log
20
28
  } from "./chunk-W5CD7WTX.js";
21
29
 
22
30
  // src/commands/install.ts
31
+ import { existsSync } from "fs";
23
32
  import chalk from "chalk";
24
- async function installCommand() {
33
+ async function installCommand(opts = {}) {
25
34
  console.log("=== \u{1F33F} Coding Friend Install \u{1F33F} ===");
26
35
  console.log();
27
36
  if (!commandExists("claude")) {
@@ -30,6 +39,14 @@ async function installCommand() {
30
39
  );
31
40
  process.exit(1);
32
41
  }
42
+ if (existsSync(devStatePath())) {
43
+ log.warn("Dev mode is currently active.");
44
+ log.dim(
45
+ `Run ${chalk.bold("cf dev off")} first, then install. Or use ${chalk.bold("cf dev sync")} to update the dev plugin.`
46
+ );
47
+ return;
48
+ }
49
+ const scope = await resolveScope(opts);
33
50
  if (isMarketplaceRegistered()) {
34
51
  log.success("Marketplace already registered.");
35
52
  } else {
@@ -49,12 +66,14 @@ async function installCommand() {
49
66
  log.success("Marketplace added.");
50
67
  }
51
68
  const installedVersion = getInstalledVersion();
52
- if (!installedVersion) {
53
- log.step("Installing plugin...");
69
+ if (!installedVersion || scope !== "user") {
70
+ log.step(`Installing plugin (${chalk.cyan(scope)} scope)...`);
54
71
  const result = run("claude", [
55
72
  "plugin",
56
73
  "install",
57
- "coding-friend@coding-friend-marketplace"
74
+ "coding-friend@coding-friend-marketplace",
75
+ "--scope",
76
+ scope
58
77
  ]);
59
78
  if (result === null) {
60
79
  log.error(
@@ -67,6 +86,11 @@ async function installCommand() {
67
86
  log.success(
68
87
  `Plugin already installed (${chalk.green(`v${installedVersion}`)}).`
69
88
  );
89
+ if (isPluginDisabled(scope)) {
90
+ log.warn(
91
+ `Plugin is installed but disabled at ${chalk.cyan(scope)} scope. Run ${chalk.bold(`cf enable --${scope}`)} to re-enable.`
92
+ );
93
+ }
70
94
  const latestVersion = getLatestVersion();
71
95
  if (latestVersion) {
72
96
  const cmp = semverCompare(installedVersion, latestVersion);
@@ -81,12 +105,15 @@ async function installCommand() {
81
105
  log.dim("Could not check for updates (no network or GitHub rate limit).");
82
106
  }
83
107
  }
108
+ ensureShellCompletion({ silent: false });
84
109
  console.log();
85
110
  log.info("Next steps:");
86
- log.dim(
111
+ console.log(
87
112
  ` ${chalk.cyan("cf init")} Initialize workspace (docs folders, config)`
88
113
  );
89
- log.dim(` ${chalk.cyan("cf statusline")} Setup statusline in Claude Code`);
114
+ console.log(
115
+ ` ${chalk.cyan("cf statusline")} Setup statusline in Claude Code to show more real-time info`
116
+ );
90
117
  console.log();
91
118
  log.dim("Restart Claude Code (or start a new session) to use the plugin.");
92
119
  }
@@ -3,12 +3,12 @@ import {
3
3
  } from "./chunk-RZRT7NGT.js";
4
4
  import {
5
5
  resolveDocsDir
6
- } from "./chunk-EDVZPZ3B.js";
6
+ } from "./chunk-KB4JM2EB.js";
7
7
  import "./chunk-PGLUEN7D.js";
8
8
  import {
9
9
  run
10
10
  } from "./chunk-UFGNO6CW.js";
11
- import "./chunk-HQ5BPWEV.js";
11
+ import "./chunk-RWUTFVRB.js";
12
12
  import {
13
13
  log
14
14
  } from "./chunk-W5CD7WTX.js";
@@ -3,10 +3,10 @@ import {
3
3
  applyPermissions,
4
4
  getExistingRules,
5
5
  groupByCategory
6
- } from "./chunk-4C4EKYM3.js";
6
+ } from "./chunk-56U7US6J.js";
7
7
  import {
8
8
  claudeLocalSettingsPath
9
- } from "./chunk-HQ5BPWEV.js";
9
+ } from "./chunk-RWUTFVRB.js";
10
10
  import {
11
11
  log
12
12
  } from "./chunk-W5CD7WTX.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureShellCompletion
4
- } from "./chunk-BCYBFGVD.js";
4
+ } from "./chunk-KJUGTLPQ.js";
5
5
  import "./chunk-W5CD7WTX.js";
6
6
 
7
7
  // src/postinstall.ts
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  loadConfig
3
- } from "./chunk-EDVZPZ3B.js";
3
+ } from "./chunk-KB4JM2EB.js";
4
4
  import "./chunk-PGLUEN7D.js";
5
5
  import {
6
6
  claudeSessionDir,
7
7
  encodeProjectPath,
8
8
  readJson,
9
+ resolvePath,
9
10
  writeJson
10
- } from "./chunk-HQ5BPWEV.js";
11
+ } from "./chunk-RWUTFVRB.js";
11
12
  import {
12
13
  log
13
14
  } from "./chunk-W5CD7WTX.js";
@@ -212,6 +213,7 @@ Remapped to: ${remapped}`
212
213
  });
213
214
  localProjectPath = confirmed.trim() || remapped;
214
215
  }
216
+ localProjectPath = resolvePath(localProjectPath);
215
217
  loadSession(chosen, localProjectPath, docsDir);
216
218
  log.success(`Session "${chosen.label}" loaded.`);
217
219
  log.info(`To resume, run:`);
@@ -4,11 +4,11 @@ import {
4
4
  saveStatuslineConfig,
5
5
  selectStatuslineComponents,
6
6
  writeStatuslineSettings
7
- } from "./chunk-YAOUAJZP.js";
7
+ } from "./chunk-QG6XYVJU.js";
8
8
  import {
9
9
  ALL_COMPONENT_IDS
10
10
  } from "./chunk-PGLUEN7D.js";
11
- import "./chunk-HQ5BPWEV.js";
11
+ import "./chunk-RWUTFVRB.js";
12
12
  import {
13
13
  log
14
14
  } from "./chunk-W5CD7WTX.js";
@@ -1,11 +1,14 @@
1
1
  import {
2
2
  isMarketplaceRegistered,
3
3
  isPluginInstalled
4
- } from "./chunk-TLLWOWYU.js";
4
+ } from "./chunk-ZOOFPF5I.js";
5
5
  import {
6
6
  hasShellCompletion,
7
7
  removeShellCompletion
8
- } from "./chunk-BCYBFGVD.js";
8
+ } from "./chunk-KJUGTLPQ.js";
9
+ import {
10
+ resolveScope
11
+ } from "./chunk-EMAINEYB.js";
9
12
  import {
10
13
  commandExists,
11
14
  run
@@ -18,7 +21,7 @@ import {
18
21
  marketplaceClonePath,
19
22
  readJson,
20
23
  writeJson
21
- } from "./chunk-HQ5BPWEV.js";
24
+ } from "./chunk-RWUTFVRB.js";
22
25
  import {
23
26
  log
24
27
  } from "./chunk-W5CD7WTX.js";
@@ -76,7 +79,43 @@ function displayDetection(d) {
76
79
  function nothingToRemove(d) {
77
80
  return !d.pluginInstalled && !d.marketplaceRegistered && !d.cacheExists && !d.cloneExists && !d.statuslineConfigured && !d.shellCompletionExists && !d.globalConfigExists;
78
81
  }
79
- async function uninstallCommand() {
82
+ async function uninstallScoped(scope) {
83
+ const proceed = await confirm({
84
+ message: `Uninstall Coding Friend from ${chalk.cyan(scope)} scope?`,
85
+ default: false
86
+ });
87
+ if (!proceed) {
88
+ log.info("Uninstall cancelled.");
89
+ return;
90
+ }
91
+ log.step(`Uninstalling plugin from ${scope} scope...`);
92
+ const result = run("claude", [
93
+ "plugin",
94
+ "uninstall",
95
+ PLUGIN_ID,
96
+ "--scope",
97
+ scope
98
+ ]);
99
+ if (result === null) {
100
+ const fallback = run("claude", [
101
+ "plugin",
102
+ "uninstall",
103
+ PLUGIN_NAME,
104
+ "--scope",
105
+ scope
106
+ ]);
107
+ if (fallback === null) {
108
+ log.warn(
109
+ "Could not uninstall plugin (may not be installed at this scope)."
110
+ );
111
+ return;
112
+ }
113
+ }
114
+ log.success(`Plugin uninstalled from ${scope} scope.`);
115
+ console.log();
116
+ log.dim("Restart Claude Code to complete the uninstall.");
117
+ }
118
+ async function uninstallCommand(opts = {}) {
80
119
  console.log(`
81
120
  === \u{1F44B} ${chalk.red("Coding Friend Uninstall")} \u{1F44B} ===`);
82
121
  if (!commandExists("claude")) {
@@ -86,17 +125,34 @@ async function uninstallCommand() {
86
125
  );
87
126
  return;
88
127
  }
89
- const detection = detect();
90
- if (detection.devModeActive) {
128
+ if (existsSync(devStatePath())) {
91
129
  log.warn("Dev mode is currently active.");
92
130
  log.dim(`Run ${chalk.bold("cf dev off")} first, then try again.`);
93
131
  return;
94
132
  }
133
+ const scope = await resolveScope(
134
+ opts,
135
+ "Where should the plugin be uninstalled from?"
136
+ );
137
+ if (scope === "project" || scope === "local") {
138
+ await uninstallScoped(scope);
139
+ return;
140
+ }
141
+ const detection = detect();
95
142
  if (nothingToRemove(detection)) {
96
143
  log.info("Nothing to uninstall \u2014 Coding Friend is not installed.");
97
144
  return;
98
145
  }
99
146
  displayDetection(detection);
147
+ log.warn("This removes the plugin cache and marketplace data globally.");
148
+ log.dim(
149
+ "If Coding Friend is also installed at project or local scope in other"
150
+ );
151
+ log.dim("projects, those installations may stop working.");
152
+ log.dim(
153
+ `Run ${chalk.bold("cf install --project")} in those projects to reinstall.`
154
+ );
155
+ console.log();
100
156
  const proceed = await confirm({
101
157
  message: "This will remove Coding Friend from Claude Code. Continue?",
102
158
  default: false
@@ -2,12 +2,13 @@ import {
2
2
  getLatestVersion,
3
3
  semverCompare,
4
4
  updateCommand
5
- } from "./chunk-UR5XUEH5.js";
6
- import "./chunk-YAOUAJZP.js";
7
- import "./chunk-BCYBFGVD.js";
5
+ } from "./chunk-YZ7IZ46F.js";
6
+ import "./chunk-QG6XYVJU.js";
8
7
  import "./chunk-PGLUEN7D.js";
8
+ import "./chunk-KJUGTLPQ.js";
9
+ import "./chunk-EMAINEYB.js";
9
10
  import "./chunk-UFGNO6CW.js";
10
- import "./chunk-HQ5BPWEV.js";
11
+ import "./chunk-RWUTFVRB.js";
11
12
  import "./chunk-W5CD7WTX.js";
12
13
  export {
13
14
  getLatestVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-friend-cli",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "CLI for coding-friend — host learning docs, setup MCP server, initialize projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,25 +0,0 @@
1
- import {
2
- installedPluginsPath,
3
- knownMarketplacesPath,
4
- readJson
5
- } from "./chunk-HQ5BPWEV.js";
6
-
7
- // src/lib/plugin-state.ts
8
- var MARKETPLACE_NAME = "coding-friend-marketplace";
9
- var PLUGIN_NAME = "coding-friend";
10
- function isPluginInstalled() {
11
- const data = readJson(installedPluginsPath());
12
- if (!data) return false;
13
- const plugins = data.plugins ?? data;
14
- return Object.keys(plugins).some((k) => k.includes(PLUGIN_NAME));
15
- }
16
- function isMarketplaceRegistered() {
17
- const data = readJson(knownMarketplacesPath());
18
- if (!data) return false;
19
- return MARKETPLACE_NAME in data;
20
- }
21
-
22
- export {
23
- isPluginInstalled,
24
- isMarketplaceRegistered
25
- };