coding-friend-cli 1.7.0 → 1.9.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
@@ -22,6 +22,8 @@ cf uninstall # Completely remove plugin, marketplace, statusline, comple
22
22
  # 💡 Interactive — asks for confirmation before acting.
23
23
  cf init # Initialize workspace (interactive)
24
24
  # 💡 You can run this anywhere, anytime.
25
+ cf config # Manage Coding Friend configuration (interactive menu)
26
+ # 💡 Edit docsDir, language, learn settings, and more.
25
27
  cf host [path] # Build and serve learning docs at localhost:3333
26
28
  # [path] is optional, default is `docs/learn`
27
29
  cf mcp [path] # Setup MCP server for LLM integration
@@ -38,6 +40,8 @@ cf dev status # Show current dev mode (local or remote)
38
40
  cf dev sync # Sync local changes to cache (no version bump needed)
39
41
  cf dev restart # Reinstall local dev plugin (off + on)
40
42
  cf dev update # Update local dev plugin to latest version (off + on)
43
+ cf session save # Save current Claude Code session to docs/sessions/
44
+ cf session load # Load a saved session from docs/sessions/
41
45
  cf help # Show all commands
42
46
  ```
43
47
 
@@ -0,0 +1,277 @@
1
+ import {
2
+ log
3
+ } from "./chunk-W5CD7WTX.js";
4
+
5
+ // src/lib/shell-completion.ts
6
+ import {
7
+ appendFileSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ readFileSync,
11
+ rmSync,
12
+ writeFileSync
13
+ } from "fs";
14
+ import { homedir } from "os";
15
+ import { basename, join } from "path";
16
+ var MARKER_START = "# >>> coding-friend CLI completion >>>";
17
+ var MARKER_END = "# <<< coding-friend CLI completion <<<";
18
+ var BASH_BLOCK = `
19
+
20
+ ${MARKER_START}
21
+ _cf_completions() {
22
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
23
+ local prev="\${COMP_WORDS[COMP_CWORD-1]}"
24
+ local commands="install uninstall init config host mcp statusline update dev session"
25
+
26
+ # Subcommands for 'dev'
27
+ if [[ "\${COMP_WORDS[1]}" == "dev" && \${COMP_CWORD} -eq 2 ]]; then
28
+ COMPREPLY=($(compgen -W "on off status restart sync update" -- "$cur"))
29
+ return
30
+ fi
31
+
32
+ # Subcommands for 'session'
33
+ if [[ "\${COMP_WORDS[1]}" == "session" && \${COMP_CWORD} -eq 2 ]]; then
34
+ COMPREPLY=($(compgen -W "save load" -- "$cur"))
35
+ return
36
+ fi
37
+
38
+ # Path completion for 'dev on|restart|update'
39
+ if [[ "\${COMP_WORDS[1]}" == "dev" && ("$prev" == "on" || "$prev" == "restart" || "$prev" == "update") ]]; then
40
+ COMPREPLY=($(compgen -d -- "$cur"))
41
+ return
42
+ fi
43
+
44
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
45
+ }
46
+ complete -o default -F _cf_completions cf
47
+ ${MARKER_END}
48
+ `;
49
+ var ZSH_FUNCTION_BODY = `_cf() {
50
+ local -a commands
51
+ commands=(
52
+ 'install:Install the Coding Friend plugin into Claude Code'
53
+ 'uninstall:Uninstall the Coding Friend plugin from Claude Code'
54
+ 'init:Initialize coding-friend in current project'
55
+ 'config:Manage Coding Friend configuration'
56
+ 'host:Build and serve learning docs as a static website'
57
+ 'mcp:Setup MCP server for learning docs'
58
+ 'statusline:Setup coding-friend statusline in Claude Code'
59
+ 'update:Update coding-friend plugin and refresh statusline'
60
+ 'dev:Switch between local and remote plugin for development'
61
+ 'session:Save and load Claude Code sessions across machines'
62
+ )
63
+
64
+ if (( CURRENT == 2 )); then
65
+ _describe 'command' commands
66
+ elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "dev" ]]; then
67
+ local -a subcommands
68
+ subcommands=(
69
+ 'on:Switch to local plugin source'
70
+ 'off:Switch back to remote marketplace'
71
+ 'status:Show current dev mode'
72
+ 'restart:Restart dev mode (re-apply local plugin)'
73
+ 'sync:Sync local plugin files without restarting'
74
+ 'update:Update local dev plugin to latest version'
75
+ )
76
+ _describe 'subcommand' subcommands
77
+ elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "session" ]]; then
78
+ local -a subcommands
79
+ subcommands=(
80
+ 'save:Save current session to docs/sessions/'
81
+ 'load:Load a saved session from docs/sessions/'
82
+ )
83
+ _describe 'subcommand' subcommands
84
+ elif (( CURRENT == 4 )) && [[ "\${words[2]}" == "dev" && ("\${words[3]}" == "on" || "\${words[3]}" == "restart" || "\${words[3]}" == "update") ]]; then
85
+ _path_files -/
86
+ fi
87
+ }
88
+ compdef _cf cf`;
89
+ function buildZshBlock(needsCompinit) {
90
+ const compinit = needsCompinit ? "autoload -Uz compinit && compinit\n" : "";
91
+ return `
92
+
93
+ ${MARKER_START}
94
+ ${compinit}${ZSH_FUNCTION_BODY}
95
+ ${MARKER_END}
96
+ `;
97
+ }
98
+ var FISH_CONTENT = `# coding-friend CLI completions
99
+ complete -c cf -f
100
+ complete -c cf -n "__fish_use_subcommand" -a install -d "Install the Coding Friend plugin into Claude Code"
101
+ complete -c cf -n "__fish_use_subcommand" -a uninstall -d "Uninstall the Coding Friend plugin from Claude Code"
102
+ complete -c cf -n "__fish_use_subcommand" -a init -d "Initialize coding-friend in current project"
103
+ complete -c cf -n "__fish_use_subcommand" -a config -d "Manage Coding Friend configuration"
104
+ complete -c cf -n "__fish_use_subcommand" -a host -d "Build and serve learning docs as a static website"
105
+ complete -c cf -n "__fish_use_subcommand" -a mcp -d "Setup MCP server for learning docs"
106
+ complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend statusline in Claude Code"
107
+ complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
108
+ complete -c cf -n "__fish_use_subcommand" -a dev -d "Switch between local and remote plugin for development"
109
+ complete -c cf -n "__fish_use_subcommand" -a session -d "Save and load Claude Code sessions across machines"
110
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a on -d "Switch to local plugin source"
111
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a off -d "Switch back to remote marketplace"
112
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a status -d "Show current dev mode"
113
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a restart -d "Restart dev mode"
114
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a sync -d "Sync local plugin files"
115
+ complete -c cf -n "__fish_seen_subcommand_from dev" -a update -d "Update local dev plugin"
116
+ complete -c cf -n "__fish_seen_subcommand_from session" -a save -d "Save current session to docs/sessions/"
117
+ complete -c cf -n "__fish_seen_subcommand_from session" -a load -d "Load a saved session from docs/sessions/"
118
+ `;
119
+ var POWERSHELL_BLOCK = `
120
+
121
+ ${MARKER_START}
122
+ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
123
+ param($wordToComplete, $commandAst, $cursorPosition)
124
+ $commands = @('install','uninstall','init','config','host','mcp','statusline','update','dev','session')
125
+ $devSubcommands = @('on','off','status','restart','sync','update')
126
+ $sessionSubcommands = @('save','load')
127
+ $words = $commandAst.CommandElements
128
+ if ($words.Count -ge 2 -and $words[1].ToString() -eq 'dev') {
129
+ $devSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
130
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
131
+ } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'session') {
132
+ $sessionSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
133
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
134
+ } else {
135
+ $commands | Where-Object { $_ -like "$wordToComplete*" } |
136
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
137
+ }
138
+ }
139
+ ${MARKER_END}
140
+ `;
141
+ function detectShell() {
142
+ if (process.platform === "win32") return "powershell";
143
+ const shell = process.env.SHELL ?? "";
144
+ if (shell.includes("zsh")) return "zsh";
145
+ if (shell.includes("bash")) return "bash";
146
+ if (shell.includes("fish")) return "fish";
147
+ return "unsupported";
148
+ }
149
+ function getRcPath(shell) {
150
+ const home = homedir();
151
+ switch (shell) {
152
+ case "zsh":
153
+ return join(home, ".zshrc");
154
+ case "bash":
155
+ return process.platform === "darwin" ? join(home, ".bash_profile") : join(home, ".bashrc");
156
+ case "fish":
157
+ return join(home, ".config", "fish", "completions", "cf.fish");
158
+ case "powershell":
159
+ return join(
160
+ process.env.USERPROFILE ?? home,
161
+ "Documents",
162
+ "PowerShell",
163
+ "Microsoft.PowerShell_profile.ps1"
164
+ );
165
+ default:
166
+ return null;
167
+ }
168
+ }
169
+ function extractExistingBlock(content) {
170
+ const startIdx = content.indexOf(MARKER_START);
171
+ const endIdx = content.indexOf(MARKER_END);
172
+ if (startIdx === -1 || endIdx === -1) return null;
173
+ return content.slice(startIdx, endIdx + MARKER_END.length);
174
+ }
175
+ function replaceBlock(content, newBlock) {
176
+ const startIdx = content.indexOf(MARKER_START);
177
+ const endIdx = content.indexOf(MARKER_END);
178
+ let sliceStart = startIdx;
179
+ while (sliceStart > 0 && content[sliceStart - 1] === "\n") sliceStart--;
180
+ return content.slice(0, sliceStart) + newBlock + content.slice(endIdx + MARKER_END.length);
181
+ }
182
+ function hasShellCompletion() {
183
+ const shell = detectShell();
184
+ const rcPath = getRcPath(shell);
185
+ if (!rcPath) return false;
186
+ if (shell === "fish") return existsSync(rcPath);
187
+ if (!existsSync(rcPath)) return false;
188
+ return readFileSync(rcPath, "utf-8").includes(MARKER_START);
189
+ }
190
+ function removeShellCompletion() {
191
+ const shell = detectShell();
192
+ const rcPath = getRcPath(shell);
193
+ if (!rcPath) return false;
194
+ if (shell === "fish") {
195
+ if (!existsSync(rcPath)) return false;
196
+ rmSync(rcPath);
197
+ log.success(`Tab completion removed (${basename(rcPath)})`);
198
+ return true;
199
+ }
200
+ if (!existsSync(rcPath)) return false;
201
+ const content = readFileSync(rcPath, "utf-8");
202
+ if (!content.includes(MARKER_START)) return false;
203
+ writeFileSync(rcPath, replaceBlock(content, ""), "utf-8");
204
+ log.success(`Tab completion removed from ~/${basename(rcPath)}`);
205
+ return true;
206
+ }
207
+ function ensureShellCompletion(opts) {
208
+ const shell = detectShell();
209
+ if (shell === "unsupported") {
210
+ if (!opts?.silent)
211
+ log.warn(
212
+ `Shell not supported for tab completion (SHELL=${process.env.SHELL ?? ""}). Skipping.`
213
+ );
214
+ return false;
215
+ }
216
+ const rcPath = getRcPath(shell);
217
+ const rcName = basename(rcPath);
218
+ if (shell === "fish") {
219
+ const dir = join(homedir(), ".config", "fish", "completions");
220
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
221
+ const existing = existsSync(rcPath) ? readFileSync(rcPath, "utf-8") : null;
222
+ if (existing === FISH_CONTENT) {
223
+ if (!opts?.silent)
224
+ log.dim(`Tab completion already up-to-date (${rcName})`);
225
+ return false;
226
+ }
227
+ writeFileSync(rcPath, FISH_CONTENT, "utf-8");
228
+ if (!opts?.silent) {
229
+ log.success(`Tab completion written to ${rcPath}`);
230
+ log.dim("Open a new terminal to activate.");
231
+ }
232
+ return true;
233
+ }
234
+ let newBlock;
235
+ if (shell === "zsh") {
236
+ const existingContent = existsSync(rcPath) ? readFileSync(rcPath, "utf-8") : "";
237
+ const needsCompinit = !existingContent.includes("autoload -Uz compinit");
238
+ newBlock = buildZshBlock(needsCompinit);
239
+ } else if (shell === "powershell") {
240
+ newBlock = POWERSHELL_BLOCK;
241
+ } else {
242
+ newBlock = BASH_BLOCK;
243
+ }
244
+ if (existsSync(rcPath)) {
245
+ const content = readFileSync(rcPath, "utf-8");
246
+ if (content.includes(MARKER_START)) {
247
+ const existing = extractExistingBlock(content);
248
+ if (existing && existing.trim() === newBlock.trim()) {
249
+ if (!opts?.silent)
250
+ log.dim(`Tab completion already up-to-date in ~/${rcName}`);
251
+ return false;
252
+ }
253
+ writeFileSync(rcPath, replaceBlock(content, newBlock), "utf-8");
254
+ if (!opts?.silent) {
255
+ log.success(`Tab completion updated in ~/${rcName}`);
256
+ log.dim(
257
+ `Run \`source ~/${rcName}\` or open a new terminal to activate.`
258
+ );
259
+ }
260
+ return true;
261
+ }
262
+ }
263
+ appendFileSync(rcPath, newBlock);
264
+ if (!opts?.silent) {
265
+ log.success(`Tab completion added to ~/${rcName}`);
266
+ if (shell !== "powershell")
267
+ log.dim(`Run \`source ~/${rcName}\` or open a new terminal to activate.`);
268
+ else log.dim("Open a new PowerShell terminal to activate.");
269
+ }
270
+ return true;
271
+ }
272
+
273
+ export {
274
+ hasShellCompletion,
275
+ removeShellCompletion,
276
+ ensureShellCompletion
277
+ };
@@ -1,18 +1,16 @@
1
1
  import {
2
2
  ALL_COMPONENT_IDS,
3
3
  STATUSLINE_COMPONENTS
4
- } from "./chunk-WXBI2HUL.js";
4
+ } from "./chunk-PGLUEN7D.js";
5
5
  import {
6
6
  claudeSettingsPath,
7
7
  globalConfigPath,
8
8
  installedPluginsPath,
9
- pluginCachePath
10
- } from "./chunk-WHCJT7E2.js";
11
- import {
12
9
  mergeJson,
10
+ pluginCachePath,
13
11
  readJson,
14
12
  writeJson
15
- } from "./chunk-IUTXHCP7.js";
13
+ } from "./chunk-TPRZHSFS.js";
16
14
 
17
15
  // src/lib/statusline.ts
18
16
  import { existsSync, readdirSync } from "fs";
@@ -0,0 +1,35 @@
1
+ import {
2
+ DEFAULT_CONFIG
3
+ } from "./chunk-PGLUEN7D.js";
4
+ import {
5
+ globalConfigPath,
6
+ localConfigPath,
7
+ readJson,
8
+ resolvePath
9
+ } from "./chunk-TPRZHSFS.js";
10
+
11
+ // src/lib/config.ts
12
+ function loadConfig() {
13
+ const global = readJson(globalConfigPath());
14
+ const local = readJson(localConfigPath());
15
+ return { ...DEFAULT_CONFIG, ...global, ...local };
16
+ }
17
+ function resolveDocsDir(explicitPath) {
18
+ if (explicitPath) {
19
+ return resolvePath(explicitPath);
20
+ }
21
+ const local = readJson(localConfigPath());
22
+ if (local?.learn?.outputDir) {
23
+ return resolvePath(local.learn.outputDir);
24
+ }
25
+ const global = readJson(globalConfigPath());
26
+ if (global?.learn?.outputDir) {
27
+ return resolvePath(global.learn.outputDir);
28
+ }
29
+ return resolvePath("docs/learn");
30
+ }
31
+
32
+ export {
33
+ loadConfig,
34
+ resolveDocsDir
35
+ };
@@ -1,10 +1,8 @@
1
1
  import {
2
2
  installedPluginsPath,
3
- knownMarketplacesPath
4
- } from "./chunk-WHCJT7E2.js";
5
- import {
3
+ knownMarketplacesPath,
6
4
  readJson
7
- } from "./chunk-IUTXHCP7.js";
5
+ } from "./chunk-TPRZHSFS.js";
8
6
 
9
7
  // src/lib/plugin-state.ts
10
8
  var MARKETPLACE_NAME = "coding-friend-marketplace";
@@ -11,7 +11,6 @@ var ALL_COMPONENT_IDS = STATUSLINE_COMPONENTS.map((c) => c.id);
11
11
  var DEFAULT_CONFIG = {
12
12
  language: "en",
13
13
  docsDir: "docs",
14
- devRulesReminder: true,
15
14
  learn: {
16
15
  language: "en",
17
16
  outputDir: "docs/learn",
@@ -0,0 +1,135 @@
1
+ import {
2
+ run
3
+ } from "./chunk-UFGNO6CW.js";
4
+ import {
5
+ globalConfigPath,
6
+ localConfigPath,
7
+ readJson
8
+ } from "./chunk-TPRZHSFS.js";
9
+ import {
10
+ log
11
+ } from "./chunk-W5CD7WTX.js";
12
+
13
+ // src/lib/prompt-utils.ts
14
+ import { select, Separator } from "@inquirer/prompts";
15
+ import { existsSync } from "fs";
16
+ import chalk from "chalk";
17
+ var BACK = "__back__";
18
+ function injectBackChoice(choices, label = "Back") {
19
+ return [
20
+ ...choices,
21
+ new Separator(),
22
+ { name: label, value: BACK }
23
+ ];
24
+ }
25
+ async function askScope(label = "Save to:") {
26
+ return select({
27
+ message: label,
28
+ choices: [
29
+ { name: "Global (all projects)", value: "global" },
30
+ { name: "This project only", value: "local" },
31
+ new Separator(),
32
+ { name: "Back", value: "back" }
33
+ ]
34
+ });
35
+ }
36
+ function showConfigHint() {
37
+ console.log(chalk.dim("Config files:"));
38
+ console.log(chalk.dim(" Global: ~/.coding-friend/config.json"));
39
+ console.log(chalk.dim(" Local: .coding-friend/config.json (this project)"));
40
+ console.log(chalk.dim("Local overrides global at property level."));
41
+ console.log();
42
+ console.log(` ${chalk.yellow("[-]")} ${chalk.dim("not set anywhere")}`);
43
+ console.log(
44
+ ` ${chalk.blue("[global]")} ${chalk.dim("set in global config only")}`
45
+ );
46
+ console.log(
47
+ ` ${chalk.green("[local]")} ${chalk.dim("set in this project only")}`
48
+ );
49
+ console.log(
50
+ ` ${chalk.cyan("[both]")} ${chalk.dim("set in both places \u2014 local value takes effect")}`
51
+ );
52
+ console.log();
53
+ }
54
+ function getScopeLabel(key, globalCfg, localCfg) {
55
+ const inGlobal = globalCfg ? globalCfg[key] !== void 0 : false;
56
+ const inLocal = localCfg ? localCfg[key] !== void 0 : false;
57
+ if (inGlobal && inLocal) return "both";
58
+ if (inGlobal) return "global";
59
+ if (inLocal) return "local";
60
+ return "-";
61
+ }
62
+ function formatScopeLabel(scope) {
63
+ if (scope === "-") return chalk.yellow("[-]");
64
+ if (scope === "both") return chalk.cyan("[both]");
65
+ if (scope === "global") return chalk.blue("[global]");
66
+ if (scope === "local") return chalk.green("[local]");
67
+ return `[${scope}]`;
68
+ }
69
+ function getMergedValue(key, globalCfg, localCfg) {
70
+ const localVal = localCfg ? localCfg[key] : void 0;
71
+ if (localVal !== void 0) return localVal;
72
+ return globalCfg ? globalCfg[key] : void 0;
73
+ }
74
+ function createSubfolders(docsDir, subfolders) {
75
+ const created = [];
76
+ for (const sub of subfolders) {
77
+ const p = `${docsDir}/${sub}`;
78
+ if (!existsSync(p)) {
79
+ run("mkdir", ["-p", p]);
80
+ created.push(sub);
81
+ }
82
+ }
83
+ if (created.length > 0) {
84
+ log.success(
85
+ `Created subfolders: ${created.map((s) => `${docsDir}/${s}`).join(", ")}`
86
+ );
87
+ }
88
+ }
89
+ function applyDocsDirChange(newValue, oldValue, scope, subfolders = []) {
90
+ if (newValue === oldValue) return;
91
+ if (scope === "local") {
92
+ if (oldValue && existsSync(oldValue)) {
93
+ if (existsSync(newValue)) {
94
+ log.warn(`Folder "${newValue}" already exists \u2014 skipped rename.`);
95
+ } else {
96
+ run("mv", [oldValue, newValue]);
97
+ log.success(`Renamed "${oldValue}" \u2192 "${newValue}"`);
98
+ createSubfolders(newValue, subfolders);
99
+ }
100
+ } else if (!existsSync(newValue)) {
101
+ run("mkdir", ["-p", newValue]);
102
+ log.success(`Created "${newValue}"`);
103
+ createSubfolders(newValue, subfolders);
104
+ }
105
+ } else {
106
+ const localCfg = readJson(localConfigPath());
107
+ if (localCfg?.docsDir) return;
108
+ const globalCfg = readJson(globalConfigPath());
109
+ const oldGlobalDir = globalCfg?.docsDir;
110
+ if (oldGlobalDir && existsSync(oldGlobalDir)) {
111
+ if (existsSync(newValue)) {
112
+ log.warn(`Folder "${newValue}" already exists \u2014 skipped rename.`);
113
+ } else {
114
+ run("mv", [oldGlobalDir, newValue]);
115
+ log.success(`Renamed "${oldGlobalDir}" \u2192 "${newValue}"`);
116
+ createSubfolders(newValue, subfolders);
117
+ }
118
+ } else if (!existsSync(newValue)) {
119
+ run("mkdir", ["-p", newValue]);
120
+ log.success(`Created "${newValue}"`);
121
+ createSubfolders(newValue, subfolders);
122
+ }
123
+ }
124
+ }
125
+
126
+ export {
127
+ BACK,
128
+ injectBackChoice,
129
+ askScope,
130
+ showConfigHint,
131
+ getScopeLabel,
132
+ formatScopeLabel,
133
+ getMergedValue,
134
+ applyDocsDirChange
135
+ };
@@ -0,0 +1,18 @@
1
+ // src/lib/lib-path.ts
2
+ import { existsSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6
+ function getLibPath(name) {
7
+ const bundled = join(__dirname, "..", "lib", name);
8
+ if (existsSync(bundled)) return bundled;
9
+ const dev = join(__dirname, "..", "..", "lib", name);
10
+ if (existsSync(dev)) return dev;
11
+ throw new Error(
12
+ `Could not find lib/${name}. Ensure it exists in the CLI package.`
13
+ );
14
+ }
15
+
16
+ export {
17
+ getLibPath
18
+ };
@@ -1,3 +1,26 @@
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
+
1
24
  // src/lib/paths.ts
2
25
  import { homedir } from "os";
3
26
  import { resolve, join } from "path";
@@ -55,8 +78,20 @@ function marketplaceClonePath() {
55
78
  function globalConfigDir() {
56
79
  return join(homedir(), ".coding-friend");
57
80
  }
81
+ function encodeProjectPath(absolutePath) {
82
+ return absolutePath.replace(/\//g, "-");
83
+ }
84
+ function claudeProjectsDir() {
85
+ return join(homedir(), ".claude", "projects");
86
+ }
87
+ function claudeSessionDir(encodedPath) {
88
+ return join(claudeProjectsDir(), encodedPath);
89
+ }
58
90
 
59
91
  export {
92
+ readJson,
93
+ writeJson,
94
+ mergeJson,
60
95
  resolvePath,
61
96
  localConfigPath,
62
97
  globalConfigPath,
@@ -67,5 +102,7 @@ export {
67
102
  knownMarketplacesPath,
68
103
  marketplaceCachePath,
69
104
  marketplaceClonePath,
70
- globalConfigDir
105
+ globalConfigDir,
106
+ encodeProjectPath,
107
+ claudeSessionDir
71
108
  };
@@ -1,24 +1,22 @@
1
1
  import {
2
2
  ensureStatusline,
3
3
  getInstalledVersion
4
- } from "./chunk-HSQX3PKW.js";
4
+ } from "./chunk-BPLN4LDL.js";
5
5
  import {
6
6
  ensureShellCompletion
7
- } from "./chunk-4PLV2ENL.js";
7
+ } from "./chunk-7N64TDZ6.js";
8
8
  import {
9
9
  commandExists,
10
10
  run,
11
11
  sleepSync
12
12
  } from "./chunk-UFGNO6CW.js";
13
13
  import {
14
- claudeSettingsPath
15
- } from "./chunk-WHCJT7E2.js";
14
+ claudeSettingsPath,
15
+ readJson
16
+ } from "./chunk-TPRZHSFS.js";
16
17
  import {
17
18
  log
18
- } from "./chunk-6DUFTBTO.js";
19
- import {
20
- readJson
21
- } from "./chunk-IUTXHCP7.js";
19
+ } from "./chunk-W5CD7WTX.js";
22
20
 
23
21
  // src/commands/update.ts
24
22
  import { readFileSync } from "fs";
@@ -45,21 +43,27 @@ function getLatestCliVersion() {
45
43
  return run("npm", ["view", "coding-friend-cli", "version"]);
46
44
  }
47
45
  function getLatestVersion() {
48
- let tag = run("gh", [
46
+ let tag = null;
47
+ tag = run("gh", [
49
48
  "api",
50
- "repos/dinhanhthi/coding-friend/releases/latest",
49
+ "repos/dinhanhthi/coding-friend/releases?per_page=100",
51
50
  "--jq",
52
- ".tag_name"
51
+ '[.[] | select(.tag_name | test("^v[0-9]"))][0].tag_name'
53
52
  ]);
54
53
  if (!tag) {
55
54
  const json = run("curl", [
56
55
  "-s",
57
- "https://api.github.com/repos/dinhanhthi/coding-friend/releases/latest"
56
+ "https://api.github.com/repos/dinhanhthi/coding-friend/releases?per_page=100"
58
57
  ]);
59
58
  if (json) {
60
59
  try {
61
- const data = JSON.parse(json);
62
- tag = data.tag_name;
60
+ const releases = JSON.parse(json);
61
+ if (Array.isArray(releases)) {
62
+ const pluginRelease = releases.find(
63
+ (r) => /^v[0-9]/.test(r.tag_name ?? "")
64
+ );
65
+ if (pluginRelease) tag = pluginRelease.tag_name;
66
+ }
63
67
  } catch {
64
68
  }
65
69
  }
@@ -3,6 +3,7 @@ import chalk from "chalk";
3
3
  var log = {
4
4
  info: (msg) => console.log(chalk.blue("\u2139"), msg),
5
5
  success: (msg) => console.log(chalk.green("\u2714"), msg),
6
+ congrats: (msg) => console.log(chalk.green("\u{1F389}"), msg),
6
7
  warn: (msg) => console.log(chalk.yellow("\u26A0"), msg),
7
8
  error: (msg) => console.log(chalk.red("\u2716"), msg),
8
9
  step: (msg) => console.log(chalk.cyan("\u2192"), msg),