@aeriondyseti/claude-profiles 0.1.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeriondyseti/claude-profiles",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "TUI and CLI for managing Claude Code profiles",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,5 +1,5 @@
1
1
  import * as p from "@clack/prompts";
2
- import { createProfile, getAllProfiles, readSettings } from "../profiles.ts";
2
+ import { createProfile, getAllProfiles, readSettings, defaultSettings } from "../profiles.ts";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
 
@@ -56,9 +56,17 @@ export async function createProfileCommand(nameArg?: string): Promise<void> {
56
56
  if (copyFrom) {
57
57
  const settings = await readSettings(copyFrom);
58
58
  if (settings) {
59
+ const defaults = defaultSettings(name);
60
+ const merged = {
61
+ ...settings,
62
+ hooks: {
63
+ ...((settings.hooks as Record<string, unknown>) ?? {}),
64
+ ...((defaults.hooks as Record<string, unknown>) ?? {}),
65
+ },
66
+ };
59
67
  await writeFile(
60
68
  join(dir, "settings.json"),
61
- JSON.stringify(settings, null, 2) + "\n",
69
+ JSON.stringify(merged, null, 2) + "\n",
62
70
  );
63
71
  }
64
72
  }
@@ -1,6 +1,6 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import { deleteProfile } from "../profiles.ts";
3
- import { isActiveProfile } from "../utils.ts";
3
+ import { isActiveProfile, DEFAULT_PROFILE_NAME } from "../utils.ts";
4
4
  import { selectProfile } from "./shared.ts";
5
5
 
6
6
  function isCancel(value: unknown): value is symbol {
@@ -11,6 +11,11 @@ export async function deleteProfileCommand(nameArg?: string): Promise<void> {
11
11
  const profile = await selectProfile("Which profile to delete?", nameArg);
12
12
  if (!profile) return;
13
13
 
14
+ if (profile.name === DEFAULT_PROFILE_NAME) {
15
+ p.log.error(`Cannot delete the default profile.`);
16
+ return;
17
+ }
18
+
14
19
  if (isActiveProfile(profile.dir)) {
15
20
  p.log.error(
16
21
  `Cannot delete "${profile.name}" — it is the currently active profile.`,
package/src/profiles.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  copyFile,
9
9
  stat,
10
10
  } from "node:fs/promises";
11
- import { getProfileDir, discoverProfiles, profileNameFromDir } from "./utils.ts";
11
+ import { getProfileDir, discoverProfiles, profileNameFromDir, DEFAULT_PROFILE_NAME, DEFAULT_PROFILE_DIR, HOME } from "./utils.ts";
12
12
 
13
13
  export interface ProfileInfo {
14
14
  name: string;
@@ -24,11 +24,19 @@ async function exists(path: string): Promise<boolean> {
24
24
  .catch(() => false);
25
25
  }
26
26
 
27
+ function claudeJsonPath(dir: string): string {
28
+ // The default profile (~/.claude) stores .claude.json in ~ instead of inside the config dir
29
+ if (dir === DEFAULT_PROFILE_DIR) {
30
+ return join(HOME, ".claude.json");
31
+ }
32
+ return join(dir, ".claude.json");
33
+ }
34
+
27
35
  export async function readClaudeJson(
28
36
  dir: string,
29
37
  ): Promise<Record<string, unknown> | null> {
30
38
  try {
31
- const raw = await readFile(join(dir, ".claude.json"), "utf-8");
39
+ const raw = await readFile(claudeJsonPath(dir), "utf-8");
32
40
  return JSON.parse(raw);
33
41
  } catch {
34
42
  return null;
@@ -67,13 +75,37 @@ export async function getAllProfiles(): Promise<ProfileInfo[]> {
67
75
  return Promise.all(dirs.map(getProfileInfo));
68
76
  }
69
77
 
78
+ export function defaultSettings(profileName: string): Record<string, unknown> {
79
+ return {
80
+ hooks: {
81
+ SessionStart: [
82
+ {
83
+ matcher: "",
84
+ hooks: [
85
+ {
86
+ type: "command",
87
+ command: `echo "claude-profiles: active profile is '${profileName}' ($CLAUDE_CONFIG_DIR)"`,
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ },
93
+ };
94
+ }
95
+
70
96
  export async function createProfile(name: string): Promise<string> {
97
+ if (name === DEFAULT_PROFILE_NAME) {
98
+ throw new Error(`Cannot create a profile named "${DEFAULT_PROFILE_NAME}" — it refers to the built-in ~/.claude directory`);
99
+ }
71
100
  const dir = getProfileDir(name);
72
101
  if (await exists(dir)) {
73
102
  throw new Error(`Profile "${name}" already exists at ${dir}`);
74
103
  }
75
104
  await mkdir(dir, { recursive: true });
76
- await writeFile(join(dir, "settings.json"), "{}\n");
105
+ await writeFile(
106
+ join(dir, "settings.json"),
107
+ JSON.stringify(defaultSettings(name), null, 2) + "\n",
108
+ );
77
109
  return dir;
78
110
  }
79
111
 
package/src/utils.ts CHANGED
@@ -5,13 +5,19 @@ import pc from "picocolors";
5
5
 
6
6
  export const HOME = homedir();
7
7
  export const CLAUDE_DIR_PREFIX = ".claude-";
8
+ export const DEFAULT_PROFILE_NAME = "default";
9
+ export const DEFAULT_PROFILE_DIR = join(HOME, ".claude");
8
10
  export const EXCLUDED_DIRS = new Set(["worktrees"]);
9
11
 
10
12
  export function getProfileDir(name: string): string {
13
+ if (name === DEFAULT_PROFILE_NAME) return DEFAULT_PROFILE_DIR;
11
14
  return join(HOME, `${CLAUDE_DIR_PREFIX}${name}`);
12
15
  }
13
16
 
14
17
  export function profileNameFromDir(dir: string): string {
18
+ if (dir === DEFAULT_PROFILE_DIR || basename(dir) === ".claude") {
19
+ return DEFAULT_PROFILE_NAME;
20
+ }
15
21
  return basename(dir).replace(CLAUDE_DIR_PREFIX, "");
16
22
  }
17
23
 
@@ -32,6 +38,12 @@ export async function discoverProfiles(): Promise<string[]> {
32
38
  const entries = await readdir(HOME);
33
39
  const profiles: string[] = [];
34
40
 
41
+ // Include the default ~/.claude profile if it exists
42
+ const defaultStat = await stat(DEFAULT_PROFILE_DIR).catch(() => null);
43
+ if (defaultStat?.isDirectory()) {
44
+ profiles.push(DEFAULT_PROFILE_DIR);
45
+ }
46
+
35
47
  for (const entry of entries) {
36
48
  if (!entry.startsWith(CLAUDE_DIR_PREFIX)) continue;
37
49
  const name = entry.slice(CLAUDE_DIR_PREFIX.length);