@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 +1 -1
- package/src/commands/create.ts +10 -2
- package/src/commands/delete.ts +6 -1
- package/src/profiles.ts +35 -3
- package/src/utils.ts +12 -0
package/package.json
CHANGED
package/src/commands/create.ts
CHANGED
|
@@ -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(
|
|
69
|
+
JSON.stringify(merged, null, 2) + "\n",
|
|
62
70
|
);
|
|
63
71
|
}
|
|
64
72
|
}
|
package/src/commands/delete.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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);
|