coding-friend-cli 1.9.1 → 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 +19 -7
- package/dist/{chunk-7N64TDZ6.js → chunk-4JL3SR5V.js} +64 -2
- package/dist/{chunk-HFLBFX6J.js → chunk-E2XX5MIM.js} +1 -1
- package/dist/chunk-EL6BAAKL.js +198 -0
- package/dist/{chunk-QQ5SVZET.js → chunk-GJLNN6X5.js} +48 -2
- package/dist/{chunk-BPLN4LDL.js → chunk-HYLS67T7.js} +1 -1
- package/dist/{chunk-VYMXERKM.js → chunk-RN5UYDIT.js} +22 -6
- package/dist/{chunk-FYHHNX7K.js → chunk-XIZJ64JK.js} +1 -1
- package/dist/{chunk-TPRZHSFS.js → chunk-ZS7BLEYT.js} +31 -27
- package/dist/{config-VAML7F7K.js → config-GYJGDXGV.js} +10 -10
- package/dist/{dev-2GBY3GKC.js → dev-GFFXRZJC.js} +4 -4
- package/dist/{host-LOG5RPZ7.js → host-SWIWQRRL.js} +2 -2
- package/dist/index.js +25 -21
- package/dist/{init-CIEDOFNC.js → init-QRUDHFME.js} +42 -32
- package/dist/{install-D4NW3OAA.js → install-FQTMSBGK.js} +32 -11
- package/dist/{mcp-ORMYETXQ.js → mcp-H3K67N2P.js} +2 -2
- package/dist/permission-DIJMCOZX.js +168 -0
- package/dist/postinstall.js +1 -1
- package/dist/{session-74F7L5LV.js → session-2APBPDZF.js} +4 -2
- package/dist/{statusline-5HWRTSVL.js → statusline-H2WUKOJ6.js} +2 -2
- package/dist/{uninstall-SOHU5WGK.js → uninstall-A2LJR2OD.js} +50 -6
- package/dist/{update-LA4B3LN4.js → update-RJSWHCCR.js} +5 -4
- package/package.json +1 -1
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
|
|
20
|
-
|
|
21
|
-
cf
|
|
22
|
-
|
|
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)
|
|
@@ -29,11 +37,15 @@ cf host [path] # Build and serve learning docs at localhost:3333
|
|
|
29
37
|
cf mcp [path] # Setup MCP server for LLM integration
|
|
30
38
|
# [path] is optional, default is `docs/learn`
|
|
31
39
|
# This prints a JSON config snippet to add to your client's MCP
|
|
40
|
+
cf permission # Manage Claude Code permission rules for Coding Friend
|
|
41
|
+
cf permission --all # Apply all recommended permissions without prompts
|
|
32
42
|
cf statusline # Setup coding-friend statusline
|
|
33
|
-
cf update
|
|
34
|
-
cf update --cli
|
|
35
|
-
cf update --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
|
|
36
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
|
|
37
49
|
cf dev on [path] # Switch to local plugin source for development
|
|
38
50
|
cf dev off # Switch back to remote marketplace
|
|
39
51
|
cf dev status # Show current dev mode (local or remote)
|
|
@@ -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 statusline update dev session"
|
|
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
|
|
@@ -55,14 +69,38 @@ var ZSH_FUNCTION_BODY = `_cf() {
|
|
|
55
69
|
'config:Manage Coding Friend configuration'
|
|
56
70
|
'host:Build and serve learning docs as a static website'
|
|
57
71
|
'mcp:Setup MCP server for learning docs'
|
|
72
|
+
'permission:Manage Claude Code permission rules for Coding Friend'
|
|
58
73
|
'statusline:Setup coding-friend statusline in Claude Code'
|
|
59
74
|
'update:Update coding-friend plugin and refresh statusline'
|
|
60
75
|
'dev:Switch between local and remote plugin for development'
|
|
61
76
|
'session:Save and load Claude Code sessions across machines'
|
|
62
77
|
)
|
|
63
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
|
+
|
|
64
98
|
if (( CURRENT == 2 )); then
|
|
65
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
|
|
66
104
|
elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "dev" ]]; then
|
|
67
105
|
local -a subcommands
|
|
68
106
|
subcommands=(
|
|
@@ -103,16 +141,32 @@ complete -c cf -n "__fish_use_subcommand" -a init -d "Initialize coding-friend i
|
|
|
103
141
|
complete -c cf -n "__fish_use_subcommand" -a config -d "Manage Coding Friend configuration"
|
|
104
142
|
complete -c cf -n "__fish_use_subcommand" -a host -d "Build and serve learning docs as a static website"
|
|
105
143
|
complete -c cf -n "__fish_use_subcommand" -a mcp -d "Setup MCP server for learning docs"
|
|
144
|
+
complete -c cf -n "__fish_use_subcommand" -a permission -d "Manage Claude Code permission rules"
|
|
106
145
|
complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend statusline in Claude Code"
|
|
107
146
|
complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
|
|
108
147
|
complete -c cf -n "__fish_use_subcommand" -a dev -d "Switch between local and remote plugin for development"
|
|
109
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
|
|
110
163
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a on -d "Switch to local plugin source"
|
|
111
164
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a off -d "Switch back to remote marketplace"
|
|
112
165
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a status -d "Show current dev mode"
|
|
113
166
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a restart -d "Restart dev mode"
|
|
114
167
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a sync -d "Sync local plugin files"
|
|
115
168
|
complete -c cf -n "__fish_seen_subcommand_from dev" -a update -d "Update local dev plugin"
|
|
169
|
+
# Session subcommands
|
|
116
170
|
complete -c cf -n "__fish_seen_subcommand_from session" -a save -d "Save current session to docs/sessions/"
|
|
117
171
|
complete -c cf -n "__fish_seen_subcommand_from session" -a load -d "Load a saved session from docs/sessions/"
|
|
118
172
|
`;
|
|
@@ -121,9 +175,11 @@ var POWERSHELL_BLOCK = `
|
|
|
121
175
|
${MARKER_START}
|
|
122
176
|
Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
|
|
123
177
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
124
|
-
$commands = @('install','uninstall','init','config','host','mcp','statusline','update','dev','session')
|
|
178
|
+
$commands = @('install','uninstall','init','config','host','mcp','permission','statusline','update','dev','session')
|
|
125
179
|
$devSubcommands = @('on','off','status','restart','sync','update')
|
|
126
180
|
$sessionSubcommands = @('save','load')
|
|
181
|
+
$scopeFlags = @('--user','--global','--project','--local')
|
|
182
|
+
$updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
|
|
127
183
|
$words = $commandAst.CommandElements
|
|
128
184
|
if ($words.Count -ge 2 -and $words[1].ToString() -eq 'dev') {
|
|
129
185
|
$devSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
@@ -131,6 +187,12 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
|
|
|
131
187
|
} elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'session') {
|
|
132
188
|
$sessionSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
133
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', $_) }
|
|
134
196
|
} else {
|
|
135
197
|
$commands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
136
198
|
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readJson,
|
|
3
|
+
writeJson
|
|
4
|
+
} from "./chunk-ZS7BLEYT.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/permissions.ts
|
|
7
|
+
var PERMISSION_RULES = [
|
|
8
|
+
// Core (hooks & infrastructure)
|
|
9
|
+
{
|
|
10
|
+
rule: "Bash(cat:*)",
|
|
11
|
+
description: "[read-only] Read file contents (\u26A0 system-wide scope, project boundary enforced by Claude Code) \xB7 Used by: session-init hook",
|
|
12
|
+
category: "Core",
|
|
13
|
+
recommended: true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
rule: "Bash(grep:*)",
|
|
17
|
+
description: "[read-only] Search file contents (\u26A0 system-wide scope, project boundary enforced by Claude Code) \xB7 Used by: session-init hook, skills",
|
|
18
|
+
category: "Core",
|
|
19
|
+
recommended: true
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
rule: "Bash(sed:*)",
|
|
23
|
+
description: "[modify] Text transformation \xB7 Used by: session-init hook",
|
|
24
|
+
category: "Core",
|
|
25
|
+
recommended: true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
rule: "Bash(tr:*)",
|
|
29
|
+
description: "[read-only] Character translation \xB7 Used by: session-init hook",
|
|
30
|
+
category: "Core",
|
|
31
|
+
recommended: true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rule: "Bash(wc:*)",
|
|
35
|
+
description: "[read-only] Count lines/words \xB7 Used by: cf-verification, skills",
|
|
36
|
+
category: "Core",
|
|
37
|
+
recommended: true
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
rule: "Bash(mkdir:*)",
|
|
41
|
+
description: "[write] Create directories \xB7 Used by: docs folder setup",
|
|
42
|
+
category: "Core",
|
|
43
|
+
recommended: true
|
|
44
|
+
},
|
|
45
|
+
// Git Operations
|
|
46
|
+
{
|
|
47
|
+
rule: "Bash(git add:*)",
|
|
48
|
+
description: "[modify] Stage files for commit \xB7 Used by: /cf-commit, /cf-ship",
|
|
49
|
+
category: "Git",
|
|
50
|
+
recommended: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
rule: "Bash(git commit:*)",
|
|
54
|
+
description: "[modify] Create commits \xB7 Used by: /cf-commit, /cf-ship",
|
|
55
|
+
category: "Git",
|
|
56
|
+
recommended: true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
rule: "Bash(git status:*)",
|
|
60
|
+
description: "[read-only] Check working tree status \xB7 Used by: /cf-commit, /cf-review, cf-verification",
|
|
61
|
+
category: "Git",
|
|
62
|
+
recommended: true
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
rule: "Bash(git diff:*)",
|
|
66
|
+
description: "[read-only] View file changes \xB7 Used by: /cf-commit, /cf-review, cf-verification",
|
|
67
|
+
category: "Git",
|
|
68
|
+
recommended: true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
rule: "Bash(git log:*)",
|
|
72
|
+
description: "[read-only] View commit history \xB7 Used by: /cf-commit, /cf-review, cf-sys-debug",
|
|
73
|
+
category: "Git",
|
|
74
|
+
recommended: true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
rule: "Bash(git push:*)",
|
|
78
|
+
description: "[remote] Push commits to remote \xB7 Used by: /cf-ship",
|
|
79
|
+
category: "Git",
|
|
80
|
+
recommended: true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
rule: "Bash(git pull:*)",
|
|
84
|
+
description: "[remote] Pull changes from remote \xB7 Used by: /cf-ship",
|
|
85
|
+
category: "Git",
|
|
86
|
+
recommended: true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
rule: "Bash(gh pr create:*)",
|
|
90
|
+
description: "[remote] Create GitHub pull requests \xB7 Used by: /cf-ship",
|
|
91
|
+
category: "Git",
|
|
92
|
+
recommended: true
|
|
93
|
+
},
|
|
94
|
+
// Testing & Build
|
|
95
|
+
{
|
|
96
|
+
rule: "Bash(npm test:*)",
|
|
97
|
+
description: "[execute] Run test suites \xB7 Used by: cf-verification, /cf-fix, cf-tdd",
|
|
98
|
+
category: "Testing & Build",
|
|
99
|
+
recommended: true
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
rule: "Bash(npm run:*)",
|
|
103
|
+
description: "[execute] Run npm scripts (build, lint, format) \xB7 Used by: cf-verification",
|
|
104
|
+
category: "Testing & Build",
|
|
105
|
+
recommended: true
|
|
106
|
+
},
|
|
107
|
+
// Web & Research
|
|
108
|
+
{
|
|
109
|
+
rule: "WebSearch",
|
|
110
|
+
description: "[network] Perform web searches \xB7 Used by: /cf-research",
|
|
111
|
+
category: "Web & Research",
|
|
112
|
+
recommended: false
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
rule: "WebFetch(domain:*)",
|
|
116
|
+
description: "[network] Fetch content from web pages \xB7 Used by: /cf-research",
|
|
117
|
+
category: "Web & Research",
|
|
118
|
+
recommended: false
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
function getExistingRules(settingsPath) {
|
|
122
|
+
const settings = readJson(settingsPath);
|
|
123
|
+
if (!settings) return [];
|
|
124
|
+
const permissions = settings.permissions;
|
|
125
|
+
return permissions?.allow ?? [];
|
|
126
|
+
}
|
|
127
|
+
function getMissingRules(existing, rules) {
|
|
128
|
+
return rules.filter((r) => !existing.includes(r.rule));
|
|
129
|
+
}
|
|
130
|
+
function buildLearnDirRules(learnPath, autoCommit) {
|
|
131
|
+
const rules = [
|
|
132
|
+
{
|
|
133
|
+
rule: `Read(${learnPath}/**)`,
|
|
134
|
+
description: "[read-only] Read learning docs \xB7 Used by: /cf-learn",
|
|
135
|
+
category: "External Learn Directory",
|
|
136
|
+
recommended: true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
rule: `Edit(${learnPath}/**)`,
|
|
140
|
+
description: "[modify] Edit learning docs \xB7 Used by: /cf-learn",
|
|
141
|
+
category: "External Learn Directory",
|
|
142
|
+
recommended: true
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
rule: `Write(${learnPath}/**)`,
|
|
146
|
+
description: "[write] Write learning docs \xB7 Used by: /cf-learn",
|
|
147
|
+
category: "External Learn Directory",
|
|
148
|
+
recommended: true
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
if (autoCommit) {
|
|
152
|
+
const quoted = learnPath.includes(" ") ? `"${learnPath}"` : learnPath;
|
|
153
|
+
rules.push({
|
|
154
|
+
rule: `Bash(cd ${quoted} && git add:*)`,
|
|
155
|
+
description: "[modify] Stage learning docs for commit \xB7 Used by: /cf-learn auto-commit",
|
|
156
|
+
category: "External Learn Directory",
|
|
157
|
+
recommended: true
|
|
158
|
+
});
|
|
159
|
+
rules.push({
|
|
160
|
+
rule: `Bash(cd ${quoted} && git commit:*)`,
|
|
161
|
+
description: "[modify] Commit learning docs \xB7 Used by: /cf-learn auto-commit",
|
|
162
|
+
category: "External Learn Directory",
|
|
163
|
+
recommended: true
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return rules;
|
|
167
|
+
}
|
|
168
|
+
function applyPermissions(settingsPath, toAdd, toRemove) {
|
|
169
|
+
const settings = readJson(settingsPath) ?? {};
|
|
170
|
+
const permissions = settings.permissions ?? {};
|
|
171
|
+
const existing = permissions.allow ?? [];
|
|
172
|
+
const afterRemove = existing.filter((r) => !toRemove.includes(r));
|
|
173
|
+
const afterAdd = [
|
|
174
|
+
...afterRemove,
|
|
175
|
+
...toAdd.filter((r) => !afterRemove.includes(r))
|
|
176
|
+
];
|
|
177
|
+
permissions.allow = afterAdd;
|
|
178
|
+
settings.permissions = permissions;
|
|
179
|
+
writeJson(settingsPath, settings);
|
|
180
|
+
}
|
|
181
|
+
function groupByCategory(rules) {
|
|
182
|
+
const groups = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const rule of rules) {
|
|
184
|
+
const list = groups.get(rule.category) ?? [];
|
|
185
|
+
list.push(rule);
|
|
186
|
+
groups.set(rule.category, list);
|
|
187
|
+
}
|
|
188
|
+
return groups;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
PERMISSION_RULES,
|
|
193
|
+
getExistingRules,
|
|
194
|
+
getMissingRules,
|
|
195
|
+
buildLearnDirRules,
|
|
196
|
+
applyPermissions,
|
|
197
|
+
groupByCategory
|
|
198
|
+
};
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
globalConfigPath,
|
|
6
6
|
localConfigPath,
|
|
7
7
|
readJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZS7BLEYT.js";
|
|
9
9
|
import {
|
|
10
10
|
log
|
|
11
11
|
} from "./chunk-W5CD7WTX.js";
|
|
@@ -26,13 +26,58 @@ async function askScope(label = "Save to:") {
|
|
|
26
26
|
return select({
|
|
27
27
|
message: label,
|
|
28
28
|
choices: [
|
|
29
|
-
{ name: "Global (all projects)", value: "global" },
|
|
30
29
|
{ name: "This project only", value: "local" },
|
|
30
|
+
{ name: "Global (all projects)", value: "global" },
|
|
31
31
|
new Separator(),
|
|
32
32
|
{ name: "Back", value: "back" }
|
|
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,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureStatusline,
|
|
3
3
|
getInstalledVersion
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HYLS67T7.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveScope
|
|
7
|
+
} from "./chunk-GJLNN6X5.js";
|
|
5
8
|
import {
|
|
6
9
|
ensureShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
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(
|
|
139
|
-
|
|
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"
|
|
@@ -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 claudeLocalSettingsPath() {
|
|
19
|
+
return resolve(process.cwd(), ".claude", "settings.local.json");
|
|
20
|
+
}
|
|
41
21
|
function installedPluginsPath() {
|
|
42
22
|
return join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
43
23
|
}
|
|
@@ -88,14 +68,35 @@ function claudeSessionDir(encodedPath) {
|
|
|
88
68
|
return join(claudeProjectsDir(), encodedPath);
|
|
89
69
|
}
|
|
90
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
|
+
|
|
91
94
|
export {
|
|
92
|
-
readJson,
|
|
93
|
-
writeJson,
|
|
94
|
-
mergeJson,
|
|
95
95
|
resolvePath,
|
|
96
96
|
localConfigPath,
|
|
97
97
|
globalConfigPath,
|
|
98
98
|
claudeSettingsPath,
|
|
99
|
+
claudeLocalSettingsPath,
|
|
99
100
|
installedPluginsPath,
|
|
100
101
|
pluginCachePath,
|
|
101
102
|
devStatePath,
|
|
@@ -104,5 +105,8 @@ export {
|
|
|
104
105
|
marketplaceClonePath,
|
|
105
106
|
globalConfigDir,
|
|
106
107
|
encodeProjectPath,
|
|
107
|
-
claudeSessionDir
|
|
108
|
+
claudeSessionDir,
|
|
109
|
+
readJson,
|
|
110
|
+
writeJson,
|
|
111
|
+
mergeJson
|
|
108
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-
|
|
11
|
-
import {
|
|
12
|
-
findStatuslineHookPath,
|
|
13
|
-
isStatuslineConfigured,
|
|
14
|
-
saveStatuslineConfig,
|
|
15
|
-
selectStatuslineComponents,
|
|
16
|
-
writeStatuslineSettings
|
|
17
|
-
} from "./chunk-BPLN4LDL.js";
|
|
17
|
+
} from "./chunk-GJLNN6X5.js";
|
|
18
18
|
import {
|
|
19
19
|
ensureShellCompletion,
|
|
20
20
|
hasShellCompletion,
|
|
21
21
|
removeShellCompletion
|
|
22
|
-
} from "./chunk-
|
|
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-
|
|
36
|
+
} from "./chunk-ZS7BLEYT.js";
|
|
37
37
|
import {
|
|
38
38
|
log
|
|
39
39
|
} from "./chunk-W5CD7WTX.js";
|