coding-friend-cli 1.10.0 → 1.11.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,18 @@ 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.
23
31
  cf init # Initialize workspace (interactive)
24
32
  # 💡 You can run this anywhere, anytime.
25
33
  cf config # Manage Coding Friend configuration (interactive menu)
@@ -32,10 +40,12 @@ cf mcp [path] # Setup MCP server for LLM integration
32
40
  cf permission # Manage Claude Code permission rules for Coding Friend
33
41
  cf permission --all # Apply all recommended permissions without prompts
34
42
  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
43
+ cf update # Update plugin + CLI + statusline
44
+ cf update --cli # Update only the CLI (npm package)
45
+ cf update --plugin # Update only the Claude Code plugin
38
46
  cf update --statusline # Update only the statusline
47
+ cf update --project # Update plugin at project scope
48
+ cf update --local # Update plugin at local scope
39
49
  cf dev on [path] # Switch to local plugin source for development
40
50
  cf dev off # Switch back to remote marketplace
41
51
  cf dev status # Show current dev mode (local or remote)
@@ -22,6 +22,8 @@ _cf_completions() {
22
22
  local cur="\${COMP_WORDS[COMP_CWORD]}"
23
23
  local prev="\${COMP_WORDS[COMP_CWORD-1]}"
24
24
  local commands="install uninstall 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
47
+ if [[ "\${COMP_WORDS[1]}" == "install" || "\${COMP_WORDS[1]}" == "uninstall" ]] && [[ "$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
@@ -62,8 +76,31 @@ var ZSH_FUNCTION_BODY = `_cf() {
62
76
  'session:Save and load Claude Code sessions across machines'
63
77
  )
64
78
 
79
+ local -a scope_flags
80
+ scope_flags=(
81
+ '--user[Install at user scope (all projects)]'
82
+ '--global[Install at user scope (all projects)]'
83
+ '--project[Install at project scope (shared via git)]'
84
+ '--local[Install at local scope (this machine only)]'
85
+ )
86
+
87
+ local -a update_flags
88
+ update_flags=(
89
+ '--cli[Update only the CLI (npm package)]'
90
+ '--plugin[Update only the Claude Code plugin]'
91
+ '--statusline[Update only the statusline]'
92
+ '--user[Update plugin at user scope (all projects)]'
93
+ '--global[Update plugin at user scope (all projects)]'
94
+ '--project[Update plugin at project scope]'
95
+ '--local[Update plugin at local scope]'
96
+ )
97
+
65
98
  if (( CURRENT == 2 )); then
66
99
  _describe 'command' commands
100
+ elif (( CURRENT >= 3 )) && [[ "\${words[2]}" == "install" || "\${words[2]}" == "uninstall" ]]; then
101
+ _values 'flags' $scope_flags
102
+ elif (( CURRENT >= 3 )) && [[ "\${words[2]}" == "update" ]]; then
103
+ _values 'flags' $update_flags
67
104
  elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "dev" ]]; then
68
105
  local -a subcommands
69
106
  subcommands=(
@@ -109,12 +146,27 @@ complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend
109
146
  complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
110
147
  complete -c cf -n "__fish_use_subcommand" -a dev -d "Switch between local and remote plugin for development"
111
148
  complete -c cf -n "__fish_use_subcommand" -a session -d "Save and load Claude Code sessions across machines"
149
+ # Scope flags for install/uninstall
150
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall" -l user -d "User scope (all projects)"
151
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall" -l global -d "User scope (all projects)"
152
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall" -l project -d "Project scope (shared via git)"
153
+ complete -c cf -n "__fish_seen_subcommand_from install uninstall" -l local -d "Local scope (this machine only)"
154
+ # Flags for update
155
+ complete -c cf -n "__fish_seen_subcommand_from update" -l cli -d "Update only the CLI"
156
+ complete -c cf -n "__fish_seen_subcommand_from update" -l plugin -d "Update only the plugin"
157
+ complete -c cf -n "__fish_seen_subcommand_from update" -l statusline -d "Update only the statusline"
158
+ complete -c cf -n "__fish_seen_subcommand_from update" -l user -d "User scope (all projects)"
159
+ complete -c cf -n "__fish_seen_subcommand_from update" -l global -d "User scope (all projects)"
160
+ complete -c cf -n "__fish_seen_subcommand_from update" -l project -d "Project scope"
161
+ complete -c cf -n "__fish_seen_subcommand_from update" -l local -d "Local scope"
162
+ # Dev subcommands
112
163
  complete -c cf -n "__fish_seen_subcommand_from dev" -a on -d "Switch to local plugin source"
113
164
  complete -c cf -n "__fish_seen_subcommand_from dev" -a off -d "Switch back to remote marketplace"
114
165
  complete -c cf -n "__fish_seen_subcommand_from dev" -a status -d "Show current dev mode"
115
166
  complete -c cf -n "__fish_seen_subcommand_from dev" -a restart -d "Restart dev mode"
116
167
  complete -c cf -n "__fish_seen_subcommand_from dev" -a sync -d "Sync local plugin files"
117
168
  complete -c cf -n "__fish_seen_subcommand_from dev" -a update -d "Update local dev plugin"
169
+ # Session subcommands
118
170
  complete -c cf -n "__fish_seen_subcommand_from session" -a save -d "Save current session to docs/sessions/"
119
171
  complete -c cf -n "__fish_seen_subcommand_from session" -a load -d "Load a saved session from docs/sessions/"
120
172
  `;
@@ -126,6 +178,8 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
126
178
  $commands = @('install','uninstall','init','config','host','mcp','permission','statusline','update','dev','session')
127
179
  $devSubcommands = @('on','off','status','restart','sync','update')
128
180
  $sessionSubcommands = @('save','load')
181
+ $scopeFlags = @('--user','--global','--project','--local')
182
+ $updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
129
183
  $words = $commandAst.CommandElements
130
184
  if ($words.Count -ge 2 -and $words[1].ToString() -eq 'dev') {
131
185
  $devSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
@@ -133,6 +187,12 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
133
187
  } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'session') {
134
188
  $sessionSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
135
189
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
190
+ } elseif ($words.Count -ge 2 -and ($words[1].ToString() -eq 'install' -or $words[1].ToString() -eq 'uninstall')) {
191
+ $scopeFlags | Where-Object { $_ -like "$wordToComplete*" } |
192
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
193
+ } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'update') {
194
+ $updateFlags | Where-Object { $_ -like "$wordToComplete*" } |
195
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
136
196
  } else {
137
197
  $commands | Where-Object { $_ -like "$wordToComplete*" } |
138
198
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
@@ -2,7 +2,7 @@ import {
2
2
  installedPluginsPath,
3
3
  knownMarketplacesPath,
4
4
  readJson
5
- } from "./chunk-HQ5BPWEV.js";
5
+ } from "./chunk-ZS7BLEYT.js";
6
6
 
7
7
  // src/lib/plugin-state.ts
8
8
  var MARKETPLACE_NAME = "coding-friend-marketplace";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readJson,
3
3
  writeJson
4
- } from "./chunk-HQ5BPWEV.js";
4
+ } from "./chunk-ZS7BLEYT.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-ZS7BLEYT.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() {
37
+ return select({
38
+ message: "Where should the plugin be installed?",
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 .claude/settings.json",
46
+ value: "project"
47
+ },
48
+ {
49
+ name: "Local \u2014 this machine only, not shared (gitignored)",
50
+ value: "local"
51
+ }
52
+ ]
53
+ });
54
+ }
55
+ async function resolveScope(opts) {
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();
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,
@@ -10,7 +10,7 @@ import {
10
10
  pluginCachePath,
11
11
  readJson,
12
12
  writeJson
13
- } from "./chunk-HQ5BPWEV.js";
13
+ } from "./chunk-ZS7BLEYT.js";
14
14
 
15
15
  // src/lib/statusline.ts
16
16
  import { existsSync, readdirSync } from "fs";
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  ensureStatusline,
3
3
  getInstalledVersion
4
- } from "./chunk-YAOUAJZP.js";
4
+ } from "./chunk-HYLS67T7.js";
5
+ import {
6
+ resolveScope
7
+ } from "./chunk-GJLNN6X5.js";
5
8
  import {
6
9
  ensureShellCompletion
7
- } from "./chunk-BCYBFGVD.js";
10
+ } from "./chunk-4JL3SR5V.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-ZS7BLEYT.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"
@@ -6,7 +6,7 @@ import {
6
6
  localConfigPath,
7
7
  readJson,
8
8
  resolvePath
9
- } from "./chunk-HQ5BPWEV.js";
9
+ } from "./chunk-ZS7BLEYT.js";
10
10
 
11
11
  // src/lib/config.ts
12
12
  function loadConfig() {
@@ -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";
@@ -91,10 +68,30 @@ function claudeSessionDir(encodedPath) {
91
68
  return join(claudeProjectsDir(), encodedPath);
92
69
  }
93
70
 
71
+ // src/lib/json.ts
72
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
73
+ import { dirname } from "path";
74
+ function readJson(filePath) {
75
+ try {
76
+ const content = readFileSync(filePath, "utf-8");
77
+ return JSON.parse(content);
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+ function writeJson(filePath, data) {
83
+ const dir = dirname(filePath);
84
+ if (!existsSync(dir)) {
85
+ mkdirSync(dir, { recursive: true });
86
+ }
87
+ writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
88
+ }
89
+ function mergeJson(filePath, data) {
90
+ const existing = readJson(filePath) ?? {};
91
+ writeJson(filePath, { ...existing, ...data });
92
+ }
93
+
94
94
  export {
95
- readJson,
96
- writeJson,
97
- mergeJson,
98
95
  resolvePath,
99
96
  localConfigPath,
100
97
  globalConfigPath,
@@ -108,5 +105,8 @@ export {
108
105
  marketplaceClonePath,
109
106
  globalConfigDir,
110
107
  encodeProjectPath,
111
- claudeSessionDir
108
+ claudeSessionDir,
109
+ readJson,
110
+ writeJson,
111
+ mergeJson
112
112
  };
@@ -1,3 +1,10 @@
1
+ import {
2
+ findStatuslineHookPath,
3
+ isStatuslineConfigured,
4
+ saveStatuslineConfig,
5
+ selectStatuslineComponents,
6
+ writeStatuslineSettings
7
+ } from "./chunk-HYLS67T7.js";
1
8
  import {
2
9
  BACK,
3
10
  applyDocsDirChange,
@@ -7,19 +14,12 @@ import {
7
14
  getScopeLabel,
8
15
  injectBackChoice,
9
16
  showConfigHint
10
- } from "./chunk-AHGBESGW.js";
11
- import {
12
- findStatuslineHookPath,
13
- isStatuslineConfigured,
14
- saveStatuslineConfig,
15
- selectStatuslineComponents,
16
- writeStatuslineSettings
17
- } from "./chunk-YAOUAJZP.js";
17
+ } from "./chunk-GJLNN6X5.js";
18
18
  import {
19
19
  ensureShellCompletion,
20
20
  hasShellCompletion,
21
21
  removeShellCompletion
22
- } from "./chunk-BCYBFGVD.js";
22
+ } from "./chunk-4JL3SR5V.js";
23
23
  import {
24
24
  ALL_COMPONENT_IDS,
25
25
  DEFAULT_CONFIG
@@ -33,7 +33,7 @@ import {
33
33
  mergeJson,
34
34
  readJson,
35
35
  resolvePath
36
- } from "./chunk-HQ5BPWEV.js";
36
+ } from "./chunk-ZS7BLEYT.js";
37
37
  import {
38
38
  log
39
39
  } from "./chunk-W5CD7WTX.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  isMarketplaceRegistered,
3
3
  isPluginInstalled
4
- } from "./chunk-TLLWOWYU.js";
4
+ } from "./chunk-E2XX5MIM.js";
5
5
  import {
6
6
  ensureStatusline
7
- } from "./chunk-YAOUAJZP.js";
7
+ } from "./chunk-HYLS67T7.js";
8
8
  import {
9
9
  ensureShellCompletion
10
- } from "./chunk-BCYBFGVD.js";
10
+ } from "./chunk-4JL3SR5V.js";
11
11
  import "./chunk-PGLUEN7D.js";
12
12
  import {
13
13
  commandExists,
@@ -19,7 +19,7 @@ import {
19
19
  pluginCachePath,
20
20
  readJson,
21
21
  writeJson
22
- } from "./chunk-HQ5BPWEV.js";
22
+ } from "./chunk-ZS7BLEYT.js";
23
23
  import {
24
24
  log
25
25
  } from "./chunk-W5CD7WTX.js";
@@ -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-XIZJ64JK.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-ZS7BLEYT.js";
13
13
  import {
14
14
  log
15
15
  } from "./chunk-W5CD7WTX.js";
package/dist/index.js CHANGED
@@ -13,40 +13,40 @@ 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-FQTMSBGK.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-A2LJR2OD.js");
22
+ await uninstallCommand(opts);
23
23
  });
24
24
  program.command("init").description("Initialize coding-friend in current project").action(async () => {
25
- const { initCommand } = await import("./init-BG2ESQQC.js");
25
+ const { initCommand } = await import("./init-QRUDHFME.js");
26
26
  await initCommand();
27
27
  });
28
28
  program.command("config").description("Manage Coding Friend configuration").action(async () => {
29
- const { configCommand } = await import("./config-6P3SE3NS.js");
29
+ const { configCommand } = await import("./config-GYJGDXGV.js");
30
30
  await configCommand();
31
31
  });
32
32
  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");
33
+ const { hostCommand } = await import("./host-SWIWQRRL.js");
34
34
  await hostCommand(path, opts);
35
35
  });
36
36
  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");
37
+ const { mcpCommand } = await import("./mcp-H3K67N2P.js");
38
38
  await mcpCommand(path);
39
39
  });
40
40
  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");
41
+ const { permissionCommand } = await import("./permission-DIJMCOZX.js");
42
42
  await permissionCommand(opts);
43
43
  });
44
44
  program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
45
- const { statuslineCommand } = await import("./statusline-L2CYEV4H.js");
45
+ const { statuslineCommand } = await import("./statusline-H2WUKOJ6.js");
46
46
  await statuslineCommand();
47
47
  });
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");
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").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) => {
49
+ const { updateCommand } = await import("./update-RJSWHCCR.js");
50
50
  await updateCommand(opts);
51
51
  });
52
52
  var session = program.command("session").description("Save and load Claude Code sessions across machines");
@@ -61,11 +61,11 @@ session.command("save").description("Save current Claude Code session to sync fo
61
61
  "-s, --session-id <id>",
62
62
  "session UUID to save (default: auto-detect newest)"
63
63
  ).option("-l, --label <label>", "label for this session").action(async (opts) => {
64
- const { sessionSaveCommand } = await import("./session-STYOO4A4.js");
64
+ const { sessionSaveCommand } = await import("./session-2APBPDZF.js");
65
65
  await sessionSaveCommand(opts);
66
66
  });
67
67
  session.command("load").description("Load a saved session from sync folder").action(async () => {
68
- const { sessionLoadCommand } = await import("./session-STYOO4A4.js");
68
+ const { sessionLoadCommand } = await import("./session-2APBPDZF.js");
69
69
  await sessionLoadCommand();
70
70
  });
71
71
  var dev = program.command("dev").description("Development mode commands");
@@ -81,35 +81,35 @@ Dev subcommands:
81
81
  dev update [path] Update local dev plugin to latest version`
82
82
  );
83
83
  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");
84
+ const { devOnCommand } = await import("./dev-GFFXRZJC.js");
85
85
  await devOnCommand(path);
86
86
  });
87
87
  dev.command("off").description("Switch back to remote marketplace").action(async () => {
88
- const { devOffCommand } = await import("./dev-4NLCK6YG.js");
88
+ const { devOffCommand } = await import("./dev-GFFXRZJC.js");
89
89
  await devOffCommand();
90
90
  });
91
91
  dev.command("status").description("Show current dev mode").action(async () => {
92
- const { devStatusCommand } = await import("./dev-4NLCK6YG.js");
92
+ const { devStatusCommand } = await import("./dev-GFFXRZJC.js");
93
93
  await devStatusCommand();
94
94
  });
95
95
  dev.command("sync").description(
96
96
  "Copy local source files to plugin cache (no version bump needed)"
97
97
  ).action(async () => {
98
- const { devSyncCommand } = await import("./dev-4NLCK6YG.js");
98
+ const { devSyncCommand } = await import("./dev-GFFXRZJC.js");
99
99
  await devSyncCommand();
100
100
  });
101
101
  dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
102
102
  "[path]",
103
103
  "path to local coding-friend repo (default: saved path or cwd)"
104
104
  ).action(async (path) => {
105
- const { devRestartCommand } = await import("./dev-4NLCK6YG.js");
105
+ const { devRestartCommand } = await import("./dev-GFFXRZJC.js");
106
106
  await devRestartCommand(path);
107
107
  });
108
108
  dev.command("update").description("Update local dev plugin to latest version (off + on)").argument(
109
109
  "[path]",
110
110
  "path to local coding-friend repo (default: saved path or cwd)"
111
111
  ).action(async (path) => {
112
- const { devUpdateCommand } = await import("./dev-4NLCK6YG.js");
112
+ const { devUpdateCommand } = await import("./dev-GFFXRZJC.js");
113
113
  await devUpdateCommand(path);
114
114
  });
115
115
  program.parse();
@@ -3,7 +3,14 @@ import {
3
3
  buildLearnDirRules,
4
4
  getExistingRules,
5
5
  getMissingRules
6
- } from "./chunk-4C4EKYM3.js";
6
+ } from "./chunk-EL6BAAKL.js";
7
+ import {
8
+ findStatuslineHookPath,
9
+ isStatuslineConfigured,
10
+ saveStatuslineConfig,
11
+ selectStatuslineComponents,
12
+ writeStatuslineSettings
13
+ } from "./chunk-HYLS67T7.js";
7
14
  import {
8
15
  BACK,
9
16
  applyDocsDirChange,
@@ -13,18 +20,11 @@ import {
13
20
  getScopeLabel,
14
21
  injectBackChoice,
15
22
  showConfigHint
16
- } from "./chunk-AHGBESGW.js";
17
- import {
18
- findStatuslineHookPath,
19
- isStatuslineConfigured,
20
- saveStatuslineConfig,
21
- selectStatuslineComponents,
22
- writeStatuslineSettings
23
- } from "./chunk-YAOUAJZP.js";
23
+ } from "./chunk-GJLNN6X5.js";
24
24
  import {
25
25
  ensureShellCompletion,
26
26
  hasShellCompletion
27
- } from "./chunk-BCYBFGVD.js";
27
+ } from "./chunk-4JL3SR5V.js";
28
28
  import {
29
29
  DEFAULT_CONFIG
30
30
  } from "./chunk-PGLUEN7D.js";
@@ -38,7 +38,7 @@ import {
38
38
  mergeJson,
39
39
  readJson,
40
40
  resolvePath
41
- } from "./chunk-HQ5BPWEV.js";
41
+ } from "./chunk-ZS7BLEYT.js";
42
42
  import {
43
43
  log
44
44
  } from "./chunk-W5CD7WTX.js";
@@ -1,27 +1,35 @@
1
1
  import {
2
2
  getLatestVersion,
3
3
  semverCompare
4
- } from "./chunk-UR5XUEH5.js";
4
+ } from "./chunk-RN5UYDIT.js";
5
5
  import {
6
6
  isMarketplaceRegistered
7
- } from "./chunk-TLLWOWYU.js";
7
+ } from "./chunk-E2XX5MIM.js";
8
8
  import {
9
9
  getInstalledVersion
10
- } from "./chunk-YAOUAJZP.js";
11
- import "./chunk-BCYBFGVD.js";
10
+ } from "./chunk-HYLS67T7.js";
11
+ import {
12
+ resolveScope
13
+ } from "./chunk-GJLNN6X5.js";
14
+ import {
15
+ ensureShellCompletion
16
+ } from "./chunk-4JL3SR5V.js";
12
17
  import "./chunk-PGLUEN7D.js";
13
18
  import {
14
19
  commandExists,
15
20
  run
16
21
  } from "./chunk-UFGNO6CW.js";
17
- import "./chunk-HQ5BPWEV.js";
22
+ import {
23
+ devStatePath
24
+ } from "./chunk-ZS7BLEYT.js";
18
25
  import {
19
26
  log
20
27
  } from "./chunk-W5CD7WTX.js";
21
28
 
22
29
  // src/commands/install.ts
30
+ import { existsSync } from "fs";
23
31
  import chalk from "chalk";
24
- async function installCommand() {
32
+ async function installCommand(opts = {}) {
25
33
  console.log("=== \u{1F33F} Coding Friend Install \u{1F33F} ===");
26
34
  console.log();
27
35
  if (!commandExists("claude")) {
@@ -30,6 +38,14 @@ async function installCommand() {
30
38
  );
31
39
  process.exit(1);
32
40
  }
41
+ if (existsSync(devStatePath())) {
42
+ log.warn("Dev mode is currently active.");
43
+ log.dim(
44
+ `Run ${chalk.bold("cf dev off")} first, then install. Or use ${chalk.bold("cf dev sync")} to update the dev plugin.`
45
+ );
46
+ return;
47
+ }
48
+ const scope = await resolveScope(opts);
33
49
  if (isMarketplaceRegistered()) {
34
50
  log.success("Marketplace already registered.");
35
51
  } else {
@@ -49,12 +65,14 @@ async function installCommand() {
49
65
  log.success("Marketplace added.");
50
66
  }
51
67
  const installedVersion = getInstalledVersion();
52
- if (!installedVersion) {
53
- log.step("Installing plugin...");
68
+ if (!installedVersion || scope !== "user") {
69
+ log.step(`Installing plugin (${chalk.cyan(scope)} scope)...`);
54
70
  const result = run("claude", [
55
71
  "plugin",
56
72
  "install",
57
- "coding-friend@coding-friend-marketplace"
73
+ "coding-friend@coding-friend-marketplace",
74
+ "--scope",
75
+ scope
58
76
  ]);
59
77
  if (result === null) {
60
78
  log.error(
@@ -81,12 +99,15 @@ async function installCommand() {
81
99
  log.dim("Could not check for updates (no network or GitHub rate limit).");
82
100
  }
83
101
  }
102
+ ensureShellCompletion({ silent: false });
84
103
  console.log();
85
104
  log.info("Next steps:");
86
- log.dim(
105
+ console.log(
87
106
  ` ${chalk.cyan("cf init")} Initialize workspace (docs folders, config)`
88
107
  );
89
- log.dim(` ${chalk.cyan("cf statusline")} Setup statusline in Claude Code`);
108
+ console.log(
109
+ ` ${chalk.cyan("cf statusline")} Setup statusline in Claude Code to show more real-time info`
110
+ );
90
111
  console.log();
91
112
  log.dim("Restart Claude Code (or start a new session) to use the plugin.");
92
113
  }
@@ -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-XIZJ64JK.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-ZS7BLEYT.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-EL6BAAKL.js";
7
7
  import {
8
8
  claudeLocalSettingsPath
9
- } from "./chunk-HQ5BPWEV.js";
9
+ } from "./chunk-ZS7BLEYT.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-4JL3SR5V.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-XIZJ64JK.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-ZS7BLEYT.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-HYLS67T7.js";
8
8
  import {
9
9
  ALL_COMPONENT_IDS
10
10
  } from "./chunk-PGLUEN7D.js";
11
- import "./chunk-HQ5BPWEV.js";
11
+ import "./chunk-ZS7BLEYT.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-E2XX5MIM.js";
5
+ import {
6
+ resolveScope
7
+ } from "./chunk-GJLNN6X5.js";
5
8
  import {
6
9
  hasShellCompletion,
7
10
  removeShellCompletion
8
- } from "./chunk-BCYBFGVD.js";
11
+ } from "./chunk-4JL3SR5V.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-ZS7BLEYT.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,12 +125,17 @@ 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(opts);
134
+ if (scope === "project" || scope === "local") {
135
+ await uninstallScoped(scope);
136
+ return;
137
+ }
138
+ const detection = detect();
95
139
  if (nothingToRemove(detection)) {
96
140
  log.info("Nothing to uninstall \u2014 Coding Friend is not installed.");
97
141
  return;
@@ -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-RN5UYDIT.js";
6
+ import "./chunk-HYLS67T7.js";
7
+ import "./chunk-GJLNN6X5.js";
8
+ import "./chunk-4JL3SR5V.js";
8
9
  import "./chunk-PGLUEN7D.js";
9
10
  import "./chunk-UFGNO6CW.js";
10
- import "./chunk-HQ5BPWEV.js";
11
+ import "./chunk-ZS7BLEYT.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.11.0",
4
4
  "description": "CLI for coding-friend — host learning docs, setup MCP server, initialize projects",
5
5
  "type": "module",
6
6
  "bin": {