claudeup 4.2.0 → 4.3.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/package.json +1 -1
- package/src/data/settings-catalog.js +9 -0
- package/src/data/settings-catalog.ts +12 -1
- package/src/services/claude-settings.js +31 -3
- package/src/services/claude-settings.ts +32 -3
- package/src/services/settings-manager.js +19 -2
- package/src/services/settings-manager.ts +16 -2
- package/src/ui/renderers/settingsRenderers.js +5 -3
- package/src/ui/renderers/settingsRenderers.tsx +5 -3
package/package.json
CHANGED
|
@@ -162,6 +162,15 @@ export const SETTINGS_CATALOG = [
|
|
|
162
162
|
storage: { type: "env", key: "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS" },
|
|
163
163
|
defaultValue: "false",
|
|
164
164
|
},
|
|
165
|
+
{
|
|
166
|
+
id: "attribution",
|
|
167
|
+
name: "AI Attribution in Commits & PRs",
|
|
168
|
+
description: "Add 'Co-Authored-By: Claude' and '🤖 Generated with Claude Code' to git commits and PR descriptions",
|
|
169
|
+
category: "workflow",
|
|
170
|
+
type: "boolean",
|
|
171
|
+
storage: { type: "attribution" },
|
|
172
|
+
defaultValue: "true",
|
|
173
|
+
},
|
|
165
174
|
{
|
|
166
175
|
id: "output-style",
|
|
167
176
|
name: "Output Style",
|
|
@@ -9,7 +9,8 @@ export type SettingCategory =
|
|
|
9
9
|
export type SettingType = "boolean" | "string" | "select";
|
|
10
10
|
export type SettingStorage =
|
|
11
11
|
| { type: "env"; key: string }
|
|
12
|
-
| { type: "setting"; key: string }
|
|
12
|
+
| { type: "setting"; key: string }
|
|
13
|
+
| { type: "attribution" };
|
|
13
14
|
|
|
14
15
|
export interface SettingDefinition {
|
|
15
16
|
id: string;
|
|
@@ -204,6 +205,16 @@ export const SETTINGS_CATALOG: SettingDefinition[] = [
|
|
|
204
205
|
storage: { type: "env", key: "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS" },
|
|
205
206
|
defaultValue: "false",
|
|
206
207
|
},
|
|
208
|
+
{
|
|
209
|
+
id: "attribution",
|
|
210
|
+
name: "AI Attribution in Commits & PRs",
|
|
211
|
+
description:
|
|
212
|
+
"Add 'Co-Authored-By: Claude' and '🤖 Generated with Claude Code' to git commits and PR descriptions",
|
|
213
|
+
category: "workflow",
|
|
214
|
+
type: "boolean",
|
|
215
|
+
storage: { type: "attribution" },
|
|
216
|
+
defaultValue: "true",
|
|
217
|
+
},
|
|
207
218
|
{
|
|
208
219
|
id: "output-style",
|
|
209
220
|
name: "Output Style",
|
|
@@ -496,6 +496,35 @@ function migrateSettingsObject(settings) {
|
|
|
496
496
|
* Runs across project settings, global settings, local settings,
|
|
497
497
|
* known_marketplaces.json, and installed_plugins.json.
|
|
498
498
|
*/
|
|
499
|
+
/**
|
|
500
|
+
* Decode a ~/.claude/projects/ directory name back to a filesystem path.
|
|
501
|
+
* Claude Code encodes paths by replacing "/" with "-", which is lossy when
|
|
502
|
+
* directory names themselves contain dashes (e.g., "circl-infra" → "circl/infra").
|
|
503
|
+
* Strategy: split on "-", then greedily recombine segments by checking which
|
|
504
|
+
* combinations actually exist on disk.
|
|
505
|
+
* Returns null if no valid path can be resolved.
|
|
506
|
+
*/
|
|
507
|
+
async function decodeProjectDirName(encoded) {
|
|
508
|
+
// Split into segments: "-Users-jack-dev-circl-infra" → ["Users","jack","dev","circl","infra"]
|
|
509
|
+
const segments = encoded.replace(/^-/, "").split("-");
|
|
510
|
+
if (segments.length === 0)
|
|
511
|
+
return null;
|
|
512
|
+
// Build path greedily: at each step, try joining the next segment with a dash first
|
|
513
|
+
// (preserving directory names like "circl-infra"), fall back to slash (new path segment)
|
|
514
|
+
let current = "/" + segments[0];
|
|
515
|
+
for (let i = 1; i < segments.length; i++) {
|
|
516
|
+
const withDash = current + "-" + segments[i];
|
|
517
|
+
const withSlash = current + "/" + segments[i];
|
|
518
|
+
// Prefer dash (keeps compound names intact) if that path prefix exists
|
|
519
|
+
if (await fs.pathExists(withDash)) {
|
|
520
|
+
current = withDash;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
current = withSlash;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return (await fs.pathExists(current)) ? current : null;
|
|
527
|
+
}
|
|
499
528
|
export async function migrateMarketplaceRename(projectPath) {
|
|
500
529
|
const result = {
|
|
501
530
|
projectMigrated: 0,
|
|
@@ -664,9 +693,8 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
664
693
|
const currentProject = projectPath || process.cwd();
|
|
665
694
|
seenPaths.add(currentProject);
|
|
666
695
|
for (const entry of entries) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if (seenPaths.has(decoded))
|
|
696
|
+
const decoded = await decodeProjectDirName(entry);
|
|
697
|
+
if (!decoded || seenPaths.has(decoded))
|
|
670
698
|
continue;
|
|
671
699
|
seenPaths.add(decoded);
|
|
672
700
|
const settingsFile = path.join(decoded, ".claude", "settings.json");
|
|
@@ -731,6 +731,36 @@ export interface MigrationResult {
|
|
|
731
731
|
* Runs across project settings, global settings, local settings,
|
|
732
732
|
* known_marketplaces.json, and installed_plugins.json.
|
|
733
733
|
*/
|
|
734
|
+
/**
|
|
735
|
+
* Decode a ~/.claude/projects/ directory name back to a filesystem path.
|
|
736
|
+
* Claude Code encodes paths by replacing "/" with "-", which is lossy when
|
|
737
|
+
* directory names themselves contain dashes (e.g., "circl-infra" → "circl/infra").
|
|
738
|
+
* Strategy: split on "-", then greedily recombine segments by checking which
|
|
739
|
+
* combinations actually exist on disk.
|
|
740
|
+
* Returns null if no valid path can be resolved.
|
|
741
|
+
*/
|
|
742
|
+
async function decodeProjectDirName(encoded: string): Promise<string | null> {
|
|
743
|
+
// Split into segments: "-Users-jack-dev-circl-infra" → ["Users","jack","dev","circl","infra"]
|
|
744
|
+
const segments = encoded.replace(/^-/, "").split("-");
|
|
745
|
+
if (segments.length === 0) return null;
|
|
746
|
+
|
|
747
|
+
// Build path greedily: at each step, try joining the next segment with a dash first
|
|
748
|
+
// (preserving directory names like "circl-infra"), fall back to slash (new path segment)
|
|
749
|
+
let current = "/" + segments[0];
|
|
750
|
+
for (let i = 1; i < segments.length; i++) {
|
|
751
|
+
const withDash = current + "-" + segments[i];
|
|
752
|
+
const withSlash = current + "/" + segments[i];
|
|
753
|
+
// Prefer dash (keeps compound names intact) if that path prefix exists
|
|
754
|
+
if (await fs.pathExists(withDash)) {
|
|
755
|
+
current = withDash;
|
|
756
|
+
} else {
|
|
757
|
+
current = withSlash;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return (await fs.pathExists(current)) ? current : null;
|
|
762
|
+
}
|
|
763
|
+
|
|
734
764
|
export async function migrateMarketplaceRename(
|
|
735
765
|
projectPath?: string,
|
|
736
766
|
): Promise<MigrationResult> {
|
|
@@ -914,9 +944,8 @@ export async function migrateMarketplaceRename(
|
|
|
914
944
|
seenPaths.add(currentProject);
|
|
915
945
|
|
|
916
946
|
for (const entry of entries) {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (seenPaths.has(decoded)) continue;
|
|
947
|
+
const decoded = await decodeProjectDirName(entry);
|
|
948
|
+
if (!decoded || seenPaths.has(decoded)) continue;
|
|
920
949
|
seenPaths.add(decoded);
|
|
921
950
|
|
|
922
951
|
const settingsFile = path.join(decoded, ".claude", "settings.json");
|
|
@@ -4,7 +4,15 @@ export async function readSettingValue(setting, scope, projectPath) {
|
|
|
4
4
|
const settings = scope === "user"
|
|
5
5
|
? await readGlobalSettings()
|
|
6
6
|
: await readSettings(projectPath);
|
|
7
|
-
if (setting.storage.type === "
|
|
7
|
+
if (setting.storage.type === "attribution") {
|
|
8
|
+
// Attribution is an object: { commit: "", pr: "" } means disabled
|
|
9
|
+
const attr = settings.attribution;
|
|
10
|
+
if (attr && attr.commit === "" && attr.pr === "") {
|
|
11
|
+
return "false";
|
|
12
|
+
}
|
|
13
|
+
return undefined; // default (enabled)
|
|
14
|
+
}
|
|
15
|
+
else if (setting.storage.type === "env") {
|
|
8
16
|
const env = settings.env;
|
|
9
17
|
return env?.[setting.storage.key];
|
|
10
18
|
}
|
|
@@ -23,7 +31,16 @@ export async function writeSettingValue(setting, value, scope, projectPath) {
|
|
|
23
31
|
const settings = scope === "user"
|
|
24
32
|
? await readGlobalSettings()
|
|
25
33
|
: await readSettings(projectPath);
|
|
26
|
-
if (setting.storage.type === "
|
|
34
|
+
if (setting.storage.type === "attribution") {
|
|
35
|
+
// Boolean toggle: "false" -> write { commit: "", pr: "" }, "true"/undefined -> delete key
|
|
36
|
+
if (value === "false") {
|
|
37
|
+
settings.attribution = { commit: "", pr: "" };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
delete settings.attribution;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (setting.storage.type === "env") {
|
|
27
44
|
// Write to the "env" block in settings.json
|
|
28
45
|
settings.env = settings.env || {};
|
|
29
46
|
if (value === undefined || value === "" || value === setting.defaultValue) {
|
|
@@ -19,7 +19,14 @@ export async function readSettingValue(
|
|
|
19
19
|
? await readGlobalSettings()
|
|
20
20
|
: await readSettings(projectPath);
|
|
21
21
|
|
|
22
|
-
if (setting.storage.type === "
|
|
22
|
+
if (setting.storage.type === "attribution") {
|
|
23
|
+
// Attribution is an object: { commit: "", pr: "" } means disabled
|
|
24
|
+
const attr = (settings as any).attribution;
|
|
25
|
+
if (attr && attr.commit === "" && attr.pr === "") {
|
|
26
|
+
return "false";
|
|
27
|
+
}
|
|
28
|
+
return undefined; // default (enabled)
|
|
29
|
+
} else if (setting.storage.type === "env") {
|
|
23
30
|
const env = (settings as any).env as Record<string, string> | undefined;
|
|
24
31
|
return env?.[setting.storage.key];
|
|
25
32
|
} else {
|
|
@@ -45,7 +52,14 @@ export async function writeSettingValue(
|
|
|
45
52
|
? await readGlobalSettings()
|
|
46
53
|
: await readSettings(projectPath);
|
|
47
54
|
|
|
48
|
-
if (setting.storage.type === "
|
|
55
|
+
if (setting.storage.type === "attribution") {
|
|
56
|
+
// Boolean toggle: "false" -> write { commit: "", pr: "" }, "true"/undefined -> delete key
|
|
57
|
+
if (value === "false") {
|
|
58
|
+
(settings as any).attribution = { commit: "", pr: "" };
|
|
59
|
+
} else {
|
|
60
|
+
delete (settings as any).attribution;
|
|
61
|
+
}
|
|
62
|
+
} else if (setting.storage.type === "env") {
|
|
49
63
|
// Write to the "env" block in settings.json
|
|
50
64
|
(settings as any).env = (settings as any).env || {};
|
|
51
65
|
if (value === undefined || value === "" || value === setting.defaultValue) {
|
|
@@ -31,9 +31,11 @@ const settingRenderer = {
|
|
|
31
31
|
renderDetail: ({ item }) => {
|
|
32
32
|
const { setting } = item;
|
|
33
33
|
const scoped = item.scopedValues;
|
|
34
|
-
const storageDesc = setting.storage.type === "
|
|
35
|
-
?
|
|
36
|
-
:
|
|
34
|
+
const storageDesc = setting.storage.type === "attribution"
|
|
35
|
+
? "settings.json: attribution"
|
|
36
|
+
: setting.storage.type === "env"
|
|
37
|
+
? `env: ${setting.storage.key}`
|
|
38
|
+
: `settings.json: ${setting.storage.key}`;
|
|
37
39
|
const userValue = formatValue(setting, scoped.user);
|
|
38
40
|
const projectValue = formatValue(setting, scoped.project);
|
|
39
41
|
const userIsSet = scoped.user !== undefined && scoped.user !== "";
|
|
@@ -79,9 +79,11 @@ const settingRenderer: ItemRenderer<SettingsSettingItem> = {
|
|
|
79
79
|
const { setting } = item;
|
|
80
80
|
const scoped = item.scopedValues;
|
|
81
81
|
const storageDesc =
|
|
82
|
-
setting.storage.type === "
|
|
83
|
-
?
|
|
84
|
-
:
|
|
82
|
+
setting.storage.type === "attribution"
|
|
83
|
+
? "settings.json: attribution"
|
|
84
|
+
: setting.storage.type === "env"
|
|
85
|
+
? `env: ${setting.storage.key}`
|
|
86
|
+
: `settings.json: ${setting.storage.key}`;
|
|
85
87
|
|
|
86
88
|
const userValue = formatValue(setting, scoped.user);
|
|
87
89
|
const projectValue = formatValue(setting, scoped.project);
|