@ramarivera/coding-buddy 0.4.0-alpha.3 → 0.4.0-alpha.4

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.
@@ -25,14 +25,15 @@ import {
25
25
  readFileSync, writeFileSync, mkdirSync, existsSync,
26
26
  readdirSync, statSync, rmSync, copyFileSync,
27
27
  } from "fs";
28
- import { join } from "path";
28
+ import { dirname, join } from "path";
29
29
  import { homedir } from "os";
30
+ import { getBuddySkillDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
30
31
 
31
32
  const HOME = homedir();
32
33
  const BACKUPS_DIR = join(HOME, ".claude-buddy", "backups");
33
- const SETTINGS = join(HOME, ".claude", "settings.json");
34
- const CLAUDE_JSON = join(HOME, ".claude.json");
35
- const SKILL = join(HOME, ".claude", "skills", "buddy", "SKILL.md");
34
+ const SETTINGS = getClaudeSettingsPath();
35
+ const CLAUDE_JSON = getClaudeJsonPath();
36
+ const SKILL = join(getBuddySkillDir(), "SKILL.md");
36
37
  const STATE_DIR = join(HOME, ".claude-buddy");
37
38
 
38
39
  const RED = "\x1b[31m";
@@ -193,7 +194,7 @@ function restoreBackup(ts: string) {
193
194
  // 1. settings.json — overwrite
194
195
  const settingsBak = join(dir, "settings.json");
195
196
  if (existsSync(settingsBak)) {
196
- mkdirSync(join(HOME, ".claude"), { recursive: true });
197
+ mkdirSync(dirname(SETTINGS), { recursive: true });
197
198
  copyFileSync(settingsBak, SETTINGS);
198
199
  ok("Restored: ~/.claude/settings.json");
199
200
  }
@@ -215,7 +216,7 @@ function restoreBackup(ts: string) {
215
216
  // 3. SKILL.md
216
217
  const skillBak = join(dir, "SKILL.md");
217
218
  if (existsSync(skillBak)) {
218
- mkdirSync(join(HOME, ".claude", "skills", "buddy"), { recursive: true });
219
+ mkdirSync(dirname(SKILL), { recursive: true });
219
220
  copyFileSync(skillBak, SKILL);
220
221
  ok("Restored: ~/.claude/skills/buddy/SKILL.md");
221
222
  }
@@ -11,6 +11,7 @@
11
11
  import { readFileSync, writeFileSync, existsSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { homedir } from "os";
14
+ import { getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
14
15
 
15
16
  interface HookCommand {
16
17
  type: "command";
@@ -42,8 +43,8 @@ function ok(msg: string) { console.log(`${GREEN}✓${NC} ${msg}`); }
42
43
  function warn(msg: string) { console.log(`${YELLOW}⚠${NC} ${msg}`); }
43
44
 
44
45
  const HOME = homedir();
45
- const CLAUDE_JSON = join(HOME, ".claude.json");
46
- const SETTINGS = join(HOME, ".claude", "settings.json");
46
+ const CLAUDE_JSON = getClaudeJsonPath();
47
+ const SETTINGS = getClaudeSettingsPath();
47
48
 
48
49
  console.log(`\n${BOLD}Disabling claude-buddy...${NC}\n`);
49
50
 
@@ -54,12 +55,12 @@ try {
54
55
  delete claudeJson.mcpServers["claude-buddy"];
55
56
  if (Object.keys(claudeJson.mcpServers).length === 0) delete claudeJson.mcpServers;
56
57
  writeFileSync(CLAUDE_JSON, JSON.stringify(claudeJson, null, 2));
57
- ok("MCP server removed from ~/.claude.json");
58
+ ok(`MCP server removed from ${CLAUDE_JSON}`);
58
59
  } else {
59
60
  warn("MCP server was not registered");
60
61
  }
61
62
  } catch {
62
- warn("Could not update ~/.claude.json");
63
+ warn(`Could not update ${CLAUDE_JSON}`);
63
64
  }
64
65
 
65
66
  // 2. Remove status line + hooks from settings.json
@@ -12,9 +12,14 @@ import { readFileSync, existsSync, statSync } from "fs";
12
12
  import { execSync } from "child_process";
13
13
  import { join, resolve } from "path";
14
14
  import { homedir } from "os";
15
+ import { getBuddySkillDir, getClaudeConfigDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
15
16
 
16
17
  const PROJECT_ROOT = resolve(import.meta.dir, "../../..");
17
18
  const HOME = homedir();
19
+ const CLAUDE_DIR = getClaudeConfigDir();
20
+ const CLAUDE_JSON = getClaudeJsonPath();
21
+ const SETTINGS = getClaudeSettingsPath();
22
+ const SKILL_PATH = join(getBuddySkillDir(), "SKILL.md");
18
23
  const STATUS_SCRIPT = join(PROJECT_ROOT, "adapters", "claude", "statusline", "buddy-status.sh");
19
24
 
20
25
  const RED = "\x1b[31m";
@@ -95,8 +100,10 @@ row("tput cols", tryExec("tput cols 2>/dev/null", "(failed)"));
95
100
  section("Filesystem");
96
101
  const procExists = existsSync("/proc");
97
102
  row("/proc exists", procExists ? `${GREEN}yes${NC} (Linux)` : `${RED}no${NC} (macOS/BSD)`);
98
- row("~/.claude/ exists", existsSync(join(HOME, ".claude")) ? "yes" : "no");
99
- row("~/.claude.json exists", existsSync(join(HOME, ".claude.json")) ? "yes" : "no");
103
+ row("Claude config dir", CLAUDE_DIR);
104
+ row("Claude config dir exists", existsSync(CLAUDE_DIR) ? "yes" : "no");
105
+ row("Claude config file", CLAUDE_JSON);
106
+ row("Claude config file exists", existsSync(CLAUDE_JSON) ? "yes" : "no");
100
107
  row("~/.claude-buddy/ exists", existsSync(join(HOME, ".claude-buddy")) ? "yes" : "no");
101
108
  row("Project root", PROJECT_ROOT);
102
109
  row("Status script exists", existsSync(STATUS_SCRIPT) ? "yes" : `${RED}no${NC}`);
@@ -138,8 +145,8 @@ if (isRecord(status)) {
138
145
  // ─── settings.json ──────────────────────────────────────────────────────────
139
146
 
140
147
  section("Claude Code config");
141
- const settings = tryParseJson(tryRead(join(HOME, ".claude", "settings.json")));
142
- const claudeJson = tryParseJson(tryRead(join(HOME, ".claude.json")));
148
+ const settings = tryParseJson(tryRead(SETTINGS));
149
+ const claudeJson = tryParseJson(tryRead(CLAUDE_JSON));
143
150
 
144
151
  if (isRecord(settings) && "statusLine" in settings && settings.statusLine !== undefined) {
145
152
  console.log(` ${DIM}statusLine:${NC}`);
@@ -166,11 +173,10 @@ if (isRecord(claudeJson) && isRecord(claudeJson.mcpServers) && isRecord(claudeJs
166
173
  err("MCP server NOT registered in ~/.claude.json");
167
174
  }
168
175
 
169
- const skillPath = join(HOME, ".claude", "skills", "buddy", "SKILL.md");
170
- if (existsSync(skillPath)) {
171
- ok(`Skill installed: ${skillPath}`);
176
+ if (existsSync(SKILL_PATH)) {
177
+ ok(`Skill installed: ${SKILL_PATH}`);
172
178
  } else {
173
- err(`Skill missing: ${skillPath}`);
179
+ err(`Skill missing: ${SKILL_PATH}`);
174
180
  }
175
181
 
176
182
  // ─── Live status line test ──────────────────────────────────────────────────
@@ -5,15 +5,15 @@
5
5
  * Checks: bun, jq, ~/.claude/ directory
6
6
  */
7
7
 
8
- import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync } from "fs";
8
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, statSync } from "fs";
9
9
  import { execSync } from "child_process";
10
10
  import { join, resolve } from "path";
11
- import { homedir } from "os";
12
11
 
13
12
  import { generateBones, renderBuddy, renderFace, RARITY_STARS } from "../../../core/engine.ts";
14
13
  import { loadCompanion, saveCompanion, writeStatusState } from "../storage/state.ts";
15
14
  import { resolveUserId } from "../storage/identity.ts";
16
15
  import { generateFallbackName } from "../../../core/reactions.ts";
16
+ import { getBuddySkillDir, getClaudeConfigDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
17
17
 
18
18
  const CYAN = "\x1b[36m";
19
19
  const GREEN = "\x1b[32m";
@@ -23,9 +23,10 @@ const BOLD = "\x1b[1m";
23
23
  const DIM = "\x1b[2m";
24
24
  const NC = "\x1b[0m";
25
25
 
26
- const CLAUDE_DIR = join(homedir(), ".claude");
27
- const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
28
- const BUDDY_DIR = join(CLAUDE_DIR, "skills", "buddy");
26
+ const CLAUDE_DIR = getClaudeConfigDir();
27
+ const SETTINGS_FILE = getClaudeSettingsPath();
28
+ const BUDDY_DIR = getBuddySkillDir();
29
+ const CLAUDE_JSON = getClaudeJsonPath();
29
30
  const PROJECT_ROOT = resolve(import.meta.dir, "../../..");
30
31
 
31
32
  function banner() {
@@ -71,21 +72,23 @@ function preflight(): boolean {
71
72
  }
72
73
  }
73
74
 
74
- // Check ~/.claude/ exists
75
+ // Check Claude config dir exists and is actually a directory
75
76
  if (!existsSync(CLAUDE_DIR)) {
76
- err("~/.claude/ not found. Start Claude Code once first, then re-run.");
77
+ err(`Claude config dir not found: ${CLAUDE_DIR}`);
78
+ pass = false;
79
+ } else if (!statSync(CLAUDE_DIR).isDirectory()) {
80
+ err(`Claude config path is not a directory: ${CLAUDE_DIR}`);
77
81
  pass = false;
78
82
  } else {
79
- ok("~/.claude/ found");
83
+ ok(`Claude config dir found: ${CLAUDE_DIR}`);
80
84
  }
81
85
 
82
- // Check ~/.claude.json exists
83
- const claudeJson = join(homedir(), ".claude.json");
84
- if (!existsSync(claudeJson)) {
85
- err("~/.claude.json not found. Start Claude Code once first, then re-run.");
86
+ // Check .claude.json exists in the active Claude config location
87
+ if (!existsSync(CLAUDE_JSON)) {
88
+ err(`Claude config file not found: ${CLAUDE_JSON}`);
86
89
  pass = false;
87
90
  } else {
88
- ok("~/.claude.json found");
91
+ ok(`Claude config file found: ${CLAUDE_JSON}`);
89
92
  }
90
93
 
91
94
  return pass;
@@ -145,7 +148,7 @@ function saveSettings(settings: ClaudeSettings) {
145
148
 
146
149
  function installMcp() {
147
150
  const serverPath = join(PROJECT_ROOT, "adapters", "claude", "server", "index.ts");
148
- const claudeJsonPath = join(homedir(), ".claude.json");
151
+ const claudeJsonPath = CLAUDE_JSON;
149
152
 
150
153
  let claudeJson: ClaudeJsonConfig = {};
151
154
  try {
@@ -161,7 +164,7 @@ function installMcp() {
161
164
  };
162
165
 
163
166
  writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
164
- ok("MCP server registered in ~/.claude.json");
167
+ ok(`MCP server registered in ${claudeJsonPath}`);
165
168
  }
166
169
 
167
170
  // ─── Step 2: Install skill ──────────────────────────────────────────────────
@@ -170,7 +173,7 @@ function installSkill() {
170
173
  const srcSkill = join(PROJECT_ROOT, "adapters", "claude", "skills", "buddy", "SKILL.md");
171
174
  mkdirSync(BUDDY_DIR, { recursive: true });
172
175
  cpSync(srcSkill, join(BUDDY_DIR, "SKILL.md"), { force: true });
173
- ok("Skill installed: ~/.claude/skills/buddy/SKILL.md");
176
+ ok(`Skill installed: ${join(BUDDY_DIR, "SKILL.md")}`);
174
177
  }
175
178
 
176
179
  // ─── Step 3: Configure status line (with animation refresh) ─────────────────
@@ -14,9 +14,10 @@
14
14
  import { readFileSync, writeFileSync, existsSync, copyFileSync, chmodSync, mkdirSync } from "fs";
15
15
  import { join, dirname, resolve } from "path";
16
16
  import { homedir } from "os";
17
+ import { getClaudeSettingsPath } from "../storage/paths.ts";
17
18
 
18
19
  const HOME = homedir();
19
- const SETTINGS = join(HOME, ".claude", "settings.json");
20
+ const SETTINGS = getClaudeSettingsPath();
20
21
  const BACKUP = join(HOME, ".claude-buddy", "statusline.bak");
21
22
  const TEST_SCRIPT = join(HOME, ".claude-buddy", "test-statusline.sh");
22
23
  const SOURCE_SCRIPT = resolve(import.meta.dir, "test-statusline.sh");
@@ -41,7 +42,7 @@ if (action === "install") {
41
42
  console.log(`\n${BOLD}claude-buddy test status line installer${NC}\n`);
42
43
 
43
44
  if (!existsSync(SETTINGS)) {
44
- err("~/.claude/settings.json not found");
45
+ err(`Claude settings not found: ${SETTINGS}`);
45
46
  process.exit(1);
46
47
  }
47
48
  if (!existsSync(SOURCE_SCRIPT)) {
@@ -5,6 +5,7 @@
5
5
  import { readFileSync, writeFileSync, existsSync, rmSync, readdirSync } from "fs";
6
6
  import { join } from "path";
7
7
  import { homedir } from "os";
8
+ import { getBuddySkillDir, getClaudeConfigDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
8
9
 
9
10
  interface HookCommand {
10
11
  type: "command";
@@ -33,9 +34,10 @@ const NC = "\x1b[0m";
33
34
  function ok(msg: string) { console.log(`${GREEN}✓${NC} ${msg}`); }
34
35
  function warn(msg: string) { console.log(`${YELLOW}⚠${NC} ${msg}`); }
35
36
 
36
- const CLAUDE_DIR = join(homedir(), ".claude");
37
- const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
38
- const SKILL_DIR = join(CLAUDE_DIR, "skills", "buddy");
37
+ const CLAUDE_DIR = getClaudeConfigDir();
38
+ const SETTINGS_FILE = getClaudeSettingsPath();
39
+ const SKILL_DIR = getBuddySkillDir();
40
+ const CLAUDE_JSON = getClaudeJsonPath();
39
41
  const STATE_DIR = join(homedir(), ".claude-buddy");
40
42
 
41
43
  console.log("\nclaude-buddy uninstall\n");
@@ -69,16 +71,15 @@ try {
69
71
 
70
72
  // Remove MCP server from ~/.claude.json
71
73
  try {
72
- const claudeJsonPath = join(homedir(), ".claude.json");
73
- const claudeJson = JSON.parse(readFileSync(claudeJsonPath, "utf8"));
74
+ const claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"));
74
75
  if (claudeJson.mcpServers?.["claude-buddy"]) {
75
76
  delete claudeJson.mcpServers["claude-buddy"];
76
77
  if (Object.keys(claudeJson.mcpServers).length === 0) delete claudeJson.mcpServers;
77
- writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
78
- ok("MCP server removed from ~/.claude.json");
78
+ writeFileSync(CLAUDE_JSON, JSON.stringify(claudeJson, null, 2));
79
+ ok(`MCP server removed from ${CLAUDE_JSON}`);
79
80
  }
80
81
  } catch {
81
- warn("Could not update ~/.claude.json");
82
+ warn(`Could not update ${CLAUDE_JSON}`);
82
83
  }
83
84
 
84
85
  // Remove hooks and statusline from settings.json
@@ -1,11 +1,10 @@
1
1
  import { readFileSync } from "fs";
2
- import { join } from "path";
3
- import { homedir } from "os";
2
+ import { getClaudeJsonPath } from "./paths.ts";
4
3
 
5
4
  export function resolveUserId(): string {
6
5
  try {
7
6
  const claudeJson = JSON.parse(
8
- readFileSync(join(homedir(), ".claude.json"), "utf8"),
7
+ readFileSync(getClaudeJsonPath(), "utf8"),
9
8
  );
10
9
  return claudeJson.oauthAccount?.accountUuid ?? claudeJson.userID ?? "anon";
11
10
  } catch {
@@ -0,0 +1,24 @@
1
+ import { homedir } from "os";
2
+ import { join, resolve } from "path";
3
+
4
+ function claudeConfigDirEnv(): string | null {
5
+ const value = process.env.CLAUDE_CONFIG_DIR?.trim();
6
+ return value ? resolve(value) : null;
7
+ }
8
+
9
+ export function getClaudeConfigDir(): string {
10
+ return claudeConfigDirEnv() ?? join(homedir(), ".claude");
11
+ }
12
+
13
+ export function getClaudeJsonPath(): string {
14
+ const configDir = claudeConfigDirEnv();
15
+ return configDir ? join(configDir, ".claude.json") : join(homedir(), ".claude.json");
16
+ }
17
+
18
+ export function getClaudeSettingsPath(): string {
19
+ return join(getClaudeConfigDir(), "settings.json");
20
+ }
21
+
22
+ export function getBuddySkillDir(): string {
23
+ return join(getClaudeConfigDir(), "skills", "buddy");
24
+ }
@@ -1,8 +1,7 @@
1
1
  import { readFileSync, writeFileSync, renameSync } from "fs";
2
- import { join } from "path";
3
- import { homedir } from "os";
2
+ import { getClaudeSettingsPath } from "./paths.ts";
4
3
 
5
- export const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
4
+ export const CLAUDE_SETTINGS_PATH = getClaudeSettingsPath();
6
5
 
7
6
  export function setBuddyStatusLine(
8
7
  statusScript: string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-buddy",
3
- "version": "0.4.0-alpha.3",
3
+ "version": "0.4.0-alpha.4",
4
4
  "description": "Persistent coding companion for Claude Code and pi",
5
5
  "type": "module",
6
6
  "bin": {