claudeup 6.2.1 → 6.3.2
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 +22 -0
- package/bin/claudeup.js +6 -1
- package/package.json +6 -6
- package/src/__tests__/alias-adopt.test.ts +331 -329
- package/src/__tests__/alias-parser.test.ts +384 -376
- package/src/__tests__/alias-shell-writer.test.ts +638 -625
- package/src/__tests__/alias-store.test.ts +143 -143
- package/src/__tests__/catalog-cache-store.test.ts +265 -265
- package/src/__tests__/catalog-notice.test.ts +123 -123
- package/src/__tests__/cli-apply-seams.test.ts +12 -8
- package/src/__tests__/cli-router.test.ts +132 -3
- package/src/__tests__/cli-update-view.test.ts +27 -8
- package/src/__tests__/community-staleness.test.ts +0 -2
- package/src/__tests__/component-badge.test.ts +4 -4
- package/src/__tests__/content-drift.test.ts +5 -1
- package/src/__tests__/conventions-integration.test.ts +774 -689
- package/src/__tests__/conventions-manager.test.ts +1216 -1152
- package/src/__tests__/doctor-bins.test.ts +14 -6
- package/src/__tests__/doctor.test.ts +438 -425
- package/src/__tests__/dual-write-prevention.test.ts +277 -282
- package/src/__tests__/enabled-not-installed.test.ts +71 -63
- package/src/__tests__/file-locking.test.ts +196 -196
- package/src/__tests__/gap-fill-versions.test.ts +318 -311
- package/src/__tests__/github-budget.test.ts +170 -164
- package/src/__tests__/gitignore-detector.test.ts +160 -152
- package/src/__tests__/gitignore-fixer.test.ts +285 -247
- package/src/__tests__/gitignore-prerun.test.ts +57 -57
- package/src/__tests__/gitignore-resolver.test.ts +173 -146
- package/src/__tests__/gitignore-service.test.ts +121 -119
- package/src/__tests__/install-command.test.ts +3 -3
- package/src/__tests__/install-plan.test.ts +13 -7
- package/src/__tests__/manifest.test.ts +16 -5
- package/src/__tests__/markdown-renderer.test.ts +0 -1
- package/src/__tests__/marketplace-badge.test.ts +111 -90
- package/src/__tests__/marketplace-version.test.ts +64 -61
- package/src/__tests__/plugin-manager-fallback.test.ts +249 -239
- package/src/__tests__/plugin-requires.test.ts +18 -4
- package/src/__tests__/plugin-setup.test.ts +12 -12
- package/src/__tests__/profile-command.test.ts +1 -7
- package/src/__tests__/profile-materializer.test.ts +31 -8
- package/src/__tests__/profile-sync.test.ts +23 -5
- package/src/__tests__/registry-version-resolution.test.ts +61 -47
- package/src/__tests__/resolve-executable.test.ts +1 -1
- package/src/__tests__/resolver.test.ts +40 -15
- package/src/__tests__/scope-action.test.ts +52 -50
- package/src/__tests__/scope-squares.test.tsx +4 -5
- package/src/__tests__/skill-install-files.test.ts +111 -103
- package/src/__tests__/symlink-manager.test.ts +27 -10
- package/src/__tests__/theme-env.test.ts +281 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/update-apply.test.ts +1 -3
- package/src/__tests__/useGitignoreModal.test.ts +69 -61
- package/src/__tests__/version-snapshot.test.ts +181 -165
- package/src/cli/ansi.ts +23 -15
- package/src/cli/doctor.ts +10 -9
- package/src/cli/install.ts +3 -3
- package/src/cli/profile.ts +5 -4
- package/src/cli/prompt.ts +0 -1
- package/src/cli/router.ts +69 -2
- package/src/cli/update-view.ts +11 -6
- package/src/cli/update.ts +9 -4
- package/src/cli/upgrade.ts +6 -4
- package/src/data/alias-flags.ts +260 -260
- package/src/data/gitignore-defaults.ts +20 -20
- package/src/data/gitignore-reasons.ts +86 -92
- package/src/data/gitignore-templates.ts +14 -14
- package/src/data/predefined-profiles.ts +8 -9
- package/src/data/skill-repos.ts +43 -35
- package/src/main.tsx +29 -6
- package/src/opentui.d.ts +1 -3
- package/src/prerunner/index.ts +7 -8
- package/src/services/alias-settings.ts +20 -23
- package/src/services/alias-shell-writer.ts +800 -803
- package/src/services/alias-store.ts +77 -77
- package/src/services/claude-settings.ts +27 -18
- package/src/services/content-drift.ts +13 -3
- package/src/services/conventions-manager.ts +692 -617
- package/src/services/doctor.ts +464 -403
- package/src/services/gitignore-detector.ts +113 -103
- package/src/services/gitignore-fixer.ts +204 -189
- package/src/services/gitignore-prerun.ts +26 -26
- package/src/services/gitignore-resolver.ts +162 -144
- package/src/services/gitignore-service.ts +120 -117
- package/src/services/install-plan.ts +4 -4
- package/src/services/manifest.ts +1 -1
- package/src/services/marketplace-catalog-git.ts +5 -2
- package/src/services/marketplace-fetcher.ts +241 -235
- package/src/services/plugin-manager.ts +17 -20
- package/src/services/plugin-mcp-config.ts +2 -2
- package/src/services/plugin-requires.ts +9 -6
- package/src/services/plugin-setup.ts +7 -9
- package/src/services/plugin-version-check.ts +4 -4
- package/src/services/profile-materializer.ts +9 -5
- package/src/services/profile-sync.ts +12 -4
- package/src/services/profiles.ts +3 -3
- package/src/services/resolver.ts +10 -6
- package/src/services/settings-manager.ts +82 -46
- package/src/services/skills-manager.ts +11 -9
- package/src/services/symlink-manager.ts +2 -5
- package/src/services/toolchain.ts +3 -3
- package/src/services/update-cache.ts +1 -1
- package/src/services/update-engine.ts +4 -4
- package/src/services/update-plan.ts +9 -11
- package/src/services/version-snapshot.ts +88 -88
- package/src/types/gitignore.ts +28 -28
- package/src/ui/App.tsx +36 -36
- package/src/ui/adapters/settingsAdapter.ts +2 -2
- package/src/ui/components/CategoryHeader.tsx +1 -1
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +3 -1
- package/src/ui/components/ScrollableList.tsx +2 -1
- package/src/ui/components/StyledText.tsx +1 -1
- package/src/ui/components/layout/FooterHints.tsx +5 -3
- package/src/ui/components/layout/Panel.tsx +2 -2
- package/src/ui/components/layout/ScreenLayout.tsx +5 -4
- package/src/ui/components/modals/InputModal.tsx +7 -1
- package/src/ui/components/modals/ModalContainer.tsx +3 -3
- package/src/ui/components/modals/SelectModal.tsx +1 -1
- package/src/ui/components/primitives/DetailSection.tsx +1 -1
- package/src/ui/components/primitives/KeyValueLine.tsx +1 -1
- package/src/ui/components/primitives/ScopeSquares.tsx +2 -2
- package/src/ui/hooks/index.ts +0 -1
- package/src/ui/hooks/useGitignoreModal.ts +1 -5
- package/src/ui/hooks/useMismatchModal.ts +4 -10
- package/src/ui/renderers/cliToolRenderers.tsx +2 -2
- package/src/ui/renderers/gitignoreRenderers.tsx +2 -2
- package/src/ui/renderers/mcpRenderers.tsx +1 -1
- package/src/ui/renderers/pluginRenderers.tsx +3 -9
- package/src/ui/renderers/profileRenderers.tsx +9 -8
- package/src/ui/renderers/settingsRenderers.tsx +4 -4
- package/src/ui/renderers/skillRenderers.tsx +14 -14
- package/src/ui/screens/AliasScreen.tsx +31 -34
- package/src/ui/screens/CliToolsScreen.tsx +14 -13
- package/src/ui/screens/EnvVarsScreen.tsx +8 -8
- package/src/ui/screens/GitignoreScreen.tsx +15 -14
- package/src/ui/screens/McpRegistryScreen.tsx +10 -7
- package/src/ui/screens/McpScreen.tsx +10 -10
- package/src/ui/screens/PluginsScreen.tsx +17 -21
- package/src/ui/screens/ProfilesScreen.tsx +25 -24
- package/src/ui/screens/SkillsScreen.tsx +16 -14
- package/src/ui/state/AppContext.tsx +4 -8
- package/src/ui/state/DimensionsContext.tsx +3 -2
- package/src/ui/theme-mode.ts +20 -38
- package/src/ui/theme-resolve.ts +148 -0
- package/src/utils/clipboard.ts +5 -5
- package/src/utils/command-utils.ts +2 -2
- package/src/utils/config-dir.ts +1 -3
- package/src/utils/file-locking.ts +88 -86
- package/src/ui/hooks/useAsyncData.ts +0 -127
- package/src/ui/state/AnimationContext.tsx +0 -76
|
@@ -56,17 +56,15 @@ function scopeVersions(
|
|
|
56
56
|
info: PluginInfo | undefined,
|
|
57
57
|
): Array<[PluginScope, string]> {
|
|
58
58
|
if (!info) return [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
([scope, status]) => [scope, status!.version!] as [PluginScope, string],
|
|
69
|
-
);
|
|
59
|
+
const out: Array<[PluginScope, string]> = [];
|
|
60
|
+
for (const [scope, status] of [
|
|
61
|
+
["user", info.userScope],
|
|
62
|
+
["project", info.projectScope],
|
|
63
|
+
["local", info.localScope],
|
|
64
|
+
] as const) {
|
|
65
|
+
if (status?.version) out.push([scope, status.version]);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
/** The highest version installed across scopes, or null. */
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { promises as fs } from "node:fs";
|
|
18
|
-
import path from "node:path";
|
|
19
18
|
import os from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Sentinel recorded by Claude Code for a plugin that is installed but whose
|
|
@@ -27,41 +27,41 @@ export const UNKNOWN_VERSION = "0.0.0";
|
|
|
27
27
|
|
|
28
28
|
/** True when a version string carries no usable information. */
|
|
29
29
|
export function isKnownVersion(v: string | undefined): v is string {
|
|
30
|
-
|
|
30
|
+
return !!v && v !== UNKNOWN_VERSION;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export type PluginChangeKind = "installed" | "updated";
|
|
34
34
|
|
|
35
35
|
export interface PluginChange {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
pluginId: string;
|
|
37
|
+
kind: PluginChangeKind;
|
|
38
|
+
/** Previous version. Only set for "updated", and always a known version. */
|
|
39
|
+
from?: string;
|
|
40
|
+
/** Current version. May be UNKNOWN_VERSION — callers must not render it raw. */
|
|
41
|
+
to: string;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface SnapshotFile {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Legacy flat baseline: pluginId -> version, with no record of which project
|
|
47
|
+
* it was observed from. Kept only so an existing file can be migrated.
|
|
48
|
+
*/
|
|
49
|
+
seen?: Record<string, string>;
|
|
50
|
+
/**
|
|
51
|
+
* projectPath -> (pluginId -> installedVersion) as of the last time claudeup
|
|
52
|
+
* rendered that project.
|
|
53
|
+
*
|
|
54
|
+
* Versions are per-project (installed_plugins.json keys installs by
|
|
55
|
+
* projectPath), but this file is global. A single flat map therefore let two
|
|
56
|
+
* projects legitimately on different versions overwrite each other's
|
|
57
|
+
* baseline, and every differing plugin was badged "updated" on the next
|
|
58
|
+
* render — a change that never happened. Keying by project removes the
|
|
59
|
+
* cross-talk entirely.
|
|
60
|
+
*/
|
|
61
|
+
byProject?: Record<string, Record<string, string>>;
|
|
62
|
+
/** Set on the run that first created the baseline. */
|
|
63
|
+
seededAt: string;
|
|
64
|
+
updatedAt: string;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -72,24 +72,24 @@ interface SnapshotFile {
|
|
|
72
72
|
* That is not hypothetical: it happened while building the tests for this fix.
|
|
73
73
|
*/
|
|
74
74
|
function snapshotPath(): string {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const configDir =
|
|
76
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
77
|
+
return path.join(configDir, "claudeup-version-snapshot.json");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
async function read(): Promise<SnapshotFile | null> {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
try {
|
|
82
|
+
const raw = await fs.readFile(snapshotPath(), "utf-8");
|
|
83
|
+
const data = JSON.parse(raw) as SnapshotFile;
|
|
84
|
+
if (!data) return null;
|
|
85
|
+
const hasFlat = typeof data.seen === "object" && data.seen !== null;
|
|
86
|
+
const hasByProject =
|
|
87
|
+
typeof data.byProject === "object" && data.byProject !== null;
|
|
88
|
+
if (!hasFlat && !hasByProject) return null;
|
|
89
|
+
return data;
|
|
90
|
+
} catch {
|
|
91
|
+
return null; // absent or corrupt — treated as "no baseline yet"
|
|
92
|
+
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
@@ -111,30 +111,30 @@ async function read(): Promise<SnapshotFile | null> {
|
|
|
111
111
|
* A downgrade IS reported: rolling back is a real change worth surfacing.
|
|
112
112
|
*/
|
|
113
113
|
export function diffVersions(
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
previous: Record<string, string> | null,
|
|
115
|
+
current: Record<string, string>,
|
|
116
116
|
): PluginChange[] {
|
|
117
|
-
|
|
117
|
+
if (!previous) return [];
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const changes: PluginChange[] = [];
|
|
120
|
+
for (const [pluginId, to] of Object.entries(current)) {
|
|
121
|
+
const from = previous[pluginId];
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
// Never seen before → newly installed, not an update. There is no
|
|
124
|
+
// meaningful "from", and `to` may legitimately be UNKNOWN_VERSION.
|
|
125
|
+
if (from === undefined) {
|
|
126
|
+
changes.push({ pluginId, kind: "installed", to });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
if (from === to) continue;
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
// One side carries no real version: we learned or lost it, nothing moved.
|
|
133
|
+
if (!isKnownVersion(from) || !isKnownVersion(to)) continue;
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
changes.push({ pluginId, kind: "updated", from, to });
|
|
136
|
+
}
|
|
137
|
+
return changes;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
@@ -149,16 +149,16 @@ export function diffVersions(
|
|
|
149
149
|
* asks first, which is the closest thing to the truth it contains.
|
|
150
150
|
*/
|
|
151
151
|
export async function loadSeenVersions(
|
|
152
|
-
|
|
152
|
+
projectPath: string,
|
|
153
153
|
): Promise<Record<string, string> | null> {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
const data = await read();
|
|
155
|
+
if (!data) return null;
|
|
156
|
+
const scoped = data.byProject?.[projectPath];
|
|
157
|
+
if (scoped) return scoped;
|
|
158
|
+
// Legacy flat baseline, project unknown — use it once, then it is replaced
|
|
159
|
+
// by per-project entries on save.
|
|
160
|
+
if (data.seen && !data.byProject) return data.seen;
|
|
161
|
+
return null;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/**
|
|
@@ -167,23 +167,23 @@ export async function loadSeenVersions(
|
|
|
167
167
|
* shows again next launch.
|
|
168
168
|
*/
|
|
169
169
|
export async function saveSeenVersions(
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
projectPath: string,
|
|
171
|
+
current: Record<string, string>,
|
|
172
172
|
): Promise<void> {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
173
|
+
try {
|
|
174
|
+
const file = snapshotPath();
|
|
175
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
176
|
+
const existing = await read();
|
|
177
|
+
const now = new Date().toISOString();
|
|
178
|
+
const payload: SnapshotFile = {
|
|
179
|
+
// Other projects' baselines are preserved — writing only this project's
|
|
180
|
+
// slot is the entire point of the fix.
|
|
181
|
+
byProject: { ...(existing?.byProject ?? {}), [projectPath]: current },
|
|
182
|
+
seededAt: existing?.seededAt ?? now,
|
|
183
|
+
updatedAt: now,
|
|
184
|
+
};
|
|
185
|
+
await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf-8");
|
|
186
|
+
} catch {
|
|
187
|
+
// ignore
|
|
188
|
+
}
|
|
189
189
|
}
|
package/src/types/gitignore.ts
CHANGED
|
@@ -10,31 +10,31 @@ export type Tier = "builtin" | "global" | "project" | "profile";
|
|
|
10
10
|
|
|
11
11
|
/** A user-authored manifest, deserialized from JSON. */
|
|
12
12
|
export interface GitignoreManifest {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
/** Paths or globs that must be excluded from git. */
|
|
14
|
+
ignore: string[];
|
|
15
|
+
/** Paths that must remain tracked even if a parent is in ignore. */
|
|
16
|
+
track: string[];
|
|
17
|
+
/** Optional template names to merge in (e.g. "common-dev"). */
|
|
18
|
+
extends?: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** A single rule after tier resolution. */
|
|
22
22
|
export interface ResolvedRule {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
pattern: string;
|
|
24
|
+
action: RuleAction;
|
|
25
|
+
source: Tier;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** When two tiers disagree on a pattern, the higher-precedence tier wins. */
|
|
29
29
|
export interface ResolvedConflict {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
pattern: string;
|
|
31
|
+
winner: Tier;
|
|
32
|
+
losers: Array<{ tier: Tier; action: RuleAction }>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export interface ResolvedManifest {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
rules: ResolvedRule[];
|
|
37
|
+
conflicts: ResolvedConflict[];
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -49,20 +49,20 @@ export interface ResolvedManifest {
|
|
|
49
49
|
* covers it (and the path isn't currently tracked)
|
|
50
50
|
*/
|
|
51
51
|
export type ViolationKind =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
| "tracked-but-should-ignore"
|
|
53
|
+
| "ignored-but-should-track"
|
|
54
|
+
| "untracked-and-should-track"
|
|
55
|
+
| "missing-from-gitignore";
|
|
56
56
|
|
|
57
57
|
export interface Violation {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
kind: ViolationKind;
|
|
59
|
+
path: string;
|
|
60
|
+
/** Which tier asserted the rule that's being violated. */
|
|
61
|
+
source: Tier;
|
|
62
|
+
/**
|
|
63
|
+
* "safe" — fix is purely additive (append a .gitignore line)
|
|
64
|
+
* "destructive" — fix mutates git index (`git rm --cached`, `git add`,
|
|
65
|
+
* removing existing .gitignore lines)
|
|
66
|
+
*/
|
|
67
|
+
severity: "safe" | "destructive";
|
|
68
68
|
}
|
package/src/ui/App.tsx
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { useRenderer } from "@opentui/react";
|
|
3
1
|
import fs from "node:fs";
|
|
2
|
+
import { useRenderer } from "@opentui/react";
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { isClaudeAvailable } from "../services/claude-cli.js";
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
useModal,
|
|
9
|
-
} from "./state/AppContext.js";
|
|
10
|
-
import {
|
|
11
|
-
DimensionsProvider,
|
|
12
|
-
useDimensions,
|
|
13
|
-
} from "./state/DimensionsContext.js";
|
|
14
|
-
import { ModalContainer } from "./components/modals/index.js";
|
|
15
|
-
import {
|
|
16
|
-
PluginsScreen,
|
|
17
|
-
McpScreen,
|
|
18
|
-
McpRegistryScreen,
|
|
19
|
-
SettingsScreen,
|
|
20
|
-
CliToolsScreen,
|
|
21
|
-
ProfilesScreen,
|
|
22
|
-
SkillsScreen,
|
|
23
|
-
StylesScreen,
|
|
24
|
-
GitignoreScreen,
|
|
25
|
-
AliasScreen,
|
|
26
|
-
} from "./screens/index.js";
|
|
27
|
-
import type { Screen } from "./state/types.js";
|
|
6
|
+
migrateMarketplaceRename,
|
|
7
|
+
recoverMarketplaceSettings,
|
|
8
|
+
} from "../services/claude-settings.js";
|
|
28
9
|
import { checkGitignore } from "../services/gitignore-prerun.js";
|
|
29
10
|
import {
|
|
30
|
-
loadGitignoreState,
|
|
31
11
|
applyAllSafeFixes,
|
|
12
|
+
loadGitignoreState,
|
|
32
13
|
} from "../services/gitignore-service.js";
|
|
33
|
-
import { useGitignoreModal } from "./hooks/useGitignoreModal.js";
|
|
34
14
|
import { repairAllMarketplaces } from "../services/local-marketplace.js";
|
|
35
|
-
import {
|
|
36
|
-
migrateMarketplaceRename,
|
|
37
|
-
recoverMarketplaceSettings,
|
|
38
|
-
} from "../services/claude-settings.js";
|
|
39
15
|
import { autoAddMissingMarketplaces } from "../services/marketplace-sync.js";
|
|
40
|
-
import { isClaudeAvailable } from "../services/claude-cli.js";
|
|
41
16
|
import {
|
|
42
|
-
checkPluginVersionMismatches,
|
|
43
17
|
type VersionMismatchInfo,
|
|
18
|
+
checkPluginVersionMismatches,
|
|
44
19
|
} from "../services/plugin-version-check.js";
|
|
45
|
-
import { useMismatchModal } from "./hooks/useMismatchModal.js";
|
|
46
20
|
import {
|
|
21
|
+
type VersionCheckResult,
|
|
47
22
|
checkForUpdates,
|
|
48
23
|
getCurrentVersion,
|
|
49
|
-
type VersionCheckResult,
|
|
50
24
|
} from "../services/version-check.js";
|
|
51
|
-
import { useKeyboardHandler } from "./hooks/useKeyboardHandler.js";
|
|
52
25
|
import { ProgressBar } from "./components/layout/ProgressBar.js";
|
|
26
|
+
import { ModalContainer } from "./components/modals/index.js";
|
|
27
|
+
import { useGitignoreModal } from "./hooks/useGitignoreModal.js";
|
|
28
|
+
import { useKeyboardHandler } from "./hooks/useKeyboardHandler.js";
|
|
29
|
+
import { useMismatchModal } from "./hooks/useMismatchModal.js";
|
|
30
|
+
import {
|
|
31
|
+
AliasScreen,
|
|
32
|
+
CliToolsScreen,
|
|
33
|
+
GitignoreScreen,
|
|
34
|
+
McpRegistryScreen,
|
|
35
|
+
McpScreen,
|
|
36
|
+
PluginsScreen,
|
|
37
|
+
ProfilesScreen,
|
|
38
|
+
SettingsScreen,
|
|
39
|
+
SkillsScreen,
|
|
40
|
+
StylesScreen,
|
|
41
|
+
} from "./screens/index.js";
|
|
42
|
+
import {
|
|
43
|
+
AppProvider,
|
|
44
|
+
useApp,
|
|
45
|
+
useModal,
|
|
46
|
+
useNavigation,
|
|
47
|
+
} from "./state/AppContext.js";
|
|
48
|
+
import {
|
|
49
|
+
DimensionsProvider,
|
|
50
|
+
useDimensions,
|
|
51
|
+
} from "./state/DimensionsContext.js";
|
|
52
|
+
import type { Screen } from "./state/types.js";
|
|
53
53
|
import { theme } from "./theme.js";
|
|
54
54
|
|
|
55
55
|
export const VERSION = getCurrentVersion();
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type SettingDefinition,
|
|
5
5
|
} from "../../data/settings-catalog.js";
|
|
6
6
|
import type { ScopedSettingValues } from "../../services/settings-manager.js";
|
|
7
|
-
import {
|
|
7
|
+
import { type UiColor, theme } from "../theme.js";
|
|
8
8
|
|
|
9
9
|
// ─── Item types ────────────────────────────────────────────────────────────────
|
|
10
10
|
|
|
@@ -117,7 +117,7 @@ export function formatValue(
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (value.length > 12) {
|
|
120
|
-
return value.slice(0, 12)
|
|
120
|
+
return `${value.slice(0, 12)}…`;
|
|
121
121
|
}
|
|
122
122
|
return value;
|
|
123
123
|
}
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { type UiColor, theme } from "../theme.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Scope indicator for list items.
|
|
@@ -49,10 +49,12 @@ export function ScopeIndicator({ user, project, local }: ScopeIndicatorProps) {
|
|
|
49
49
|
<text fg={theme.colors.text}>
|
|
50
50
|
{segments.map((s, i) =>
|
|
51
51
|
s.bg ? (
|
|
52
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: segments are fixed-position scope squares (user/project/local); the slot is the identity and the list never reorders
|
|
52
53
|
<span key={i} bg={s.bg} fg={s.fg}>
|
|
53
54
|
{s.char}
|
|
54
55
|
</span>
|
|
55
56
|
) : (
|
|
57
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: same fixed-slot list as above
|
|
56
58
|
<span key={i}> </span>
|
|
57
59
|
),
|
|
58
60
|
)}
|
|
@@ -30,20 +30,22 @@ export function FooterHints({
|
|
|
30
30
|
}: { hints: FooterHint[] }): React.ReactNode {
|
|
31
31
|
const nodes: React.ReactNode[] = [];
|
|
32
32
|
hints.forEach((hint, i) => {
|
|
33
|
+
// A hint is identified by its key chord plus label, not its position.
|
|
34
|
+
const id = `${hint.keys.join("")}:${hint.label}`;
|
|
33
35
|
if (i > 0) {
|
|
34
36
|
nodes.push(
|
|
35
|
-
<span key={`gap-${
|
|
37
|
+
<span key={`gap-${id}`} fg={LABEL_FG}>
|
|
36
38
|
{GAP}
|
|
37
39
|
</span>,
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
nodes.push(
|
|
41
|
-
<span key={`k-${
|
|
43
|
+
<span key={`k-${id}`} bg={KEY_BG} fg={KEY_FG}>
|
|
42
44
|
{` ${hint.keys.join("")} `}
|
|
43
45
|
</span>,
|
|
44
46
|
);
|
|
45
47
|
nodes.push(
|
|
46
|
-
<span key={`l-${
|
|
48
|
+
<span key={`l-${id}`} fg={LABEL_FG}>
|
|
47
49
|
{` ${hint.label}`}
|
|
48
50
|
</span>,
|
|
49
51
|
);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
|
2
1
|
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
3
|
-
import
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
4
|
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
5
5
|
import { useApp } from "../../state/AppContext.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useDimensions } from "../../state/DimensionsContext.js";
|
|
7
7
|
import type { Screen } from "../../state/types.js";
|
|
8
|
-
import { FooterHints, type FooterHint } from "./FooterHints.js";
|
|
9
8
|
import { theme } from "../../theme.js";
|
|
9
|
+
import { TabBar } from "../TabBar.js";
|
|
10
|
+
import { type FooterHint, FooterHints } from "./FooterHints.js";
|
|
10
11
|
|
|
11
12
|
interface ScreenLayoutProps {
|
|
12
13
|
/** Screen title (e.g., "claudeup Plugins") */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SubmitEvent } from "@opentui/core";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { theme } from "../../theme.js";
|
|
3
4
|
|
|
@@ -53,7 +54,12 @@ export function InputModal({
|
|
|
53
54
|
>
|
|
54
55
|
<input
|
|
55
56
|
value={defaultValue}
|
|
56
|
-
onSubmit={
|
|
57
|
+
onSubmit={(value: string | SubmitEvent) => {
|
|
58
|
+
// @opentui/react types the prop as an intersection of the core
|
|
59
|
+
// `(event: SubmitEvent) => void` and its own `(value: string) => void`;
|
|
60
|
+
// at runtime the input hands over the string.
|
|
61
|
+
if (typeof value === "string") onSubmit(value);
|
|
62
|
+
}}
|
|
57
63
|
focused
|
|
58
64
|
width={50}
|
|
59
65
|
/>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { useApp } from "../../state/AppContext.js";
|
|
3
2
|
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
3
|
+
import { useApp } from "../../state/AppContext.js";
|
|
4
4
|
import { ConfirmModal } from "./ConfirmModal.js";
|
|
5
5
|
import { InputModal } from "./InputModal.js";
|
|
6
|
-
import { SelectModal } from "./SelectModal.js";
|
|
7
|
-
import { MessageModal } from "./MessageModal.js";
|
|
8
6
|
import { LoadingModal } from "./LoadingModal.js";
|
|
7
|
+
import { MessageModal } from "./MessageModal.js";
|
|
8
|
+
import { SelectModal } from "./SelectModal.js";
|
|
9
9
|
import { VersionMismatchModal } from "./VersionMismatchModal.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { SelectOption } from "../../state/types.js";
|
|
3
2
|
import { useDimensions } from "../../state/DimensionsContext.js";
|
|
3
|
+
import type { SelectOption } from "../../state/types.js";
|
|
4
4
|
import { theme } from "../../theme.js";
|
|
5
5
|
|
|
6
6
|
interface SelectModalProps {
|
|
@@ -23,8 +23,8 @@ const SEGMENT = "■";
|
|
|
23
23
|
* grey as de-emphasised body text. Against a light page that reads as a *dark*
|
|
24
24
|
* block, giving an unlit segment the same visual weight as a lit one — "non
|
|
25
25
|
* installed state is dark as well - confusing". The unlit fill now comes from
|
|
26
|
-
* `scopeOffFill()`, which steps toward
|
|
27
|
-
*
|
|
26
|
+
* `scopeOffFill()`, which steps toward the page the resolved mode names; see
|
|
27
|
+
* theme-mode.ts for why that one value cannot be fixed.
|
|
28
28
|
*/
|
|
29
29
|
export function ScopeSquares({
|
|
30
30
|
user,
|
package/src/ui/hooks/index.ts
CHANGED
|
@@ -40,11 +40,7 @@ export function buildGitignoreModal(args: BuildModalArgs): ModalState {
|
|
|
40
40
|
return {
|
|
41
41
|
type: "select",
|
|
42
42
|
title: "Gitignore violations detected",
|
|
43
|
-
message:
|
|
44
|
-
`Found ${violationCount} managed gitignore item(s) that do not match ` +
|
|
45
|
-
`the current repo state.\n\n` +
|
|
46
|
-
`Missing .gitignore entries can be added now. Items that change git ` +
|
|
47
|
-
`tracking are reviewed and fixed from the Gitignore tab.`,
|
|
43
|
+
message: `Found ${violationCount} managed gitignore item(s) that do not match the current repo state.\n\nMissing .gitignore entries can be added now. Items that change git tracking are reviewed and fixed from the Gitignore tab.`,
|
|
48
44
|
options,
|
|
49
45
|
onSelect: (value: string) => {
|
|
50
46
|
if (value === "fix-safe") onFixSafe();
|