claudeup 6.5.1 → 6.7.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 +4 -4
- package/src/__tests__/active-closure.test.ts +184 -0
- package/src/__tests__/blackbox-active-closure.test.ts +119 -0
- package/src/__tests__/blackbox-convergence.test.ts +334 -0
- package/src/__tests__/blackbox-parity.test.ts +684 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +523 -0
- package/src/__tests__/blackbox-tool-state.test.ts +669 -0
- package/src/__tests__/cli-apply-seams.test.ts +2 -0
- package/src/__tests__/cli-tool-commands.test.ts +1 -1
- package/src/__tests__/cli-tools-adapter.test.ts +294 -0
- package/src/__tests__/cli-update-view.test.ts +142 -2
- package/src/__tests__/content-drift.test.ts +60 -0
- package/src/__tests__/conventions-manager.test.ts +45 -45
- package/src/__tests__/enabled-not-installed.test.ts +2 -3
- package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
- package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
- package/src/__tests__/{gitignore-prerun.test.ts → gitignore-check.test.ts} +8 -6
- package/src/__tests__/models-presets-marketplace.test.ts +141 -0
- package/src/__tests__/plugin-cli-argv.test.ts +1 -1
- package/src/__tests__/plugin-manager-fallback.test.ts +3 -5
- package/src/__tests__/plugin-row-text.test.ts +336 -0
- package/src/__tests__/resolver.test.ts +3 -3
- package/src/__tests__/scope-action.test.ts +69 -19
- package/src/__tests__/shell-script-callers.test.ts +8 -8
- package/src/__tests__/tool-probe.test.ts +1400 -0
- package/src/__tests__/update-apply.test.ts +283 -1
- package/src/__tests__/update-plan.test.ts +773 -31
- package/src/__tests__/version-unknown-state.test.ts +388 -0
- package/src/cli/router.ts +0 -5
- package/src/cli/update-view.ts +92 -12
- package/src/cli/update.ts +114 -38
- package/src/data/models-presets.ts +10 -11
- package/src/data/predefined-profiles.ts +7 -7
- package/src/services/active-closure.ts +117 -0
- package/src/services/catalog-cache-store.ts +6 -2
- package/src/services/claude-settings.ts +2 -174
- package/src/services/community-styles.ts +1 -1
- package/src/services/{gitignore-prerun.ts → gitignore-check.ts} +14 -10
- package/src/services/gitignore-fixer.ts +1 -1
- package/src/services/marketplace-refresh.ts +30 -0
- package/src/services/plugin-manager.ts +200 -283
- package/src/services/resolver.ts +7 -2
- package/src/services/tool-probe.ts +1637 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/update-engine.ts +220 -32
- package/src/services/update-plan.ts +640 -160
- package/src/services/version-snapshot.ts +1 -1
- package/src/types/bun.d.ts +6 -0
- package/src/ui/App.tsx +2 -27
- package/src/ui/adapters/cliToolsAdapter.ts +176 -0
- package/src/ui/adapters/pluginsAdapter.ts +57 -0
- package/src/ui/components/modals/ModalContainer.tsx +0 -26
- package/src/ui/hooks/index.ts +0 -1
- package/src/ui/hooks/useGitignoreModal.ts +2 -3
- package/src/ui/renderers/cliToolRenderers.tsx +108 -67
- package/src/ui/renderers/pluginRenderers.tsx +157 -98
- package/src/ui/renderers/pluginRowText.ts +70 -0
- package/src/ui/renderers/pluginStatusText.ts +56 -0
- package/src/ui/screens/CliToolsScreen.tsx +154 -280
- package/src/ui/screens/PluginsScreen.tsx +156 -140
- package/src/ui/state/types.ts +0 -7
- package/src/__tests__/dual-write-prevention.test.ts +0 -380
- package/src/__tests__/gap-fill-versions.test.ts +0 -389
- package/src/__tests__/plugin-version-check.test.ts +0 -762
- package/src/cli/claude.ts +0 -21
- package/src/prerunner/index.ts +0 -500
- package/src/services/plugin-version-check.ts +0 -360
- package/src/services/update-cache.ts +0 -78
- package/src/ui/components/modals/VersionMismatchModal.tsx +0 -138
- package/src/ui/hooks/useMismatchModal.ts +0 -89
package/src/cli/claude.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `claudeup claude [args...]` — prerunner mode.
|
|
3
|
-
*
|
|
4
|
-
* Runs plugin/marketplace update & migration checks (1h cache, or `-f`/`--force`
|
|
5
|
-
* to bypass), then execs `claude` with the remaining args. Returns claude's exit
|
|
6
|
-
* code so the caller can propagate it.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { prerunClaude } from "../prerunner/index.js";
|
|
10
|
-
|
|
11
|
-
export async function runClaudeCommand(claudeArgs: string[]): Promise<number> {
|
|
12
|
-
const forceUpdate =
|
|
13
|
-
claudeArgs.includes("--force") || claudeArgs.includes("-f");
|
|
14
|
-
|
|
15
|
-
// Remove prerunner-specific flags before passing args to claude.
|
|
16
|
-
const filteredArgs = claudeArgs.filter(
|
|
17
|
-
(arg) => arg !== "--force" && arg !== "-f",
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
return prerunClaude(filteredArgs, { force: forceUpdate });
|
|
21
|
-
}
|
package/src/prerunner/index.ts
DELETED
|
@@ -1,500 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "fs-extra";
|
|
4
|
-
import {
|
|
5
|
-
type PluginScope,
|
|
6
|
-
isClaudeAvailable,
|
|
7
|
-
repairPlugin,
|
|
8
|
-
updatePlugin,
|
|
9
|
-
} from "../services/claude-cli.js";
|
|
10
|
-
import { runClaude } from "../services/claude-runner.js";
|
|
11
|
-
import {
|
|
12
|
-
cleanupExtraKnownMarketplaces,
|
|
13
|
-
gapFillInstalledPluginVersions,
|
|
14
|
-
migrateMarketplaceRename,
|
|
15
|
-
readGlobalSettings,
|
|
16
|
-
recoverMarketplaceSettings,
|
|
17
|
-
saveGlobalInstalledPluginVersion,
|
|
18
|
-
saveLocalInstalledPluginVersion,
|
|
19
|
-
writeGlobalSettings,
|
|
20
|
-
} from "../services/claude-settings.js";
|
|
21
|
-
import { clearContentDriftCache } from "../services/content-drift.js";
|
|
22
|
-
import {
|
|
23
|
-
checkGitignore,
|
|
24
|
-
formatPrerunWarning as formatGitignoreWarning,
|
|
25
|
-
} from "../services/gitignore-prerun.js";
|
|
26
|
-
import { refreshRegisteredMarketplaces } from "../services/marketplace-refresh.js";
|
|
27
|
-
import { autoAddMissingMarketplaces } from "../services/marketplace-sync.js";
|
|
28
|
-
import {
|
|
29
|
-
clearMarketplaceCache,
|
|
30
|
-
compareVersions,
|
|
31
|
-
getAvailablePlugins,
|
|
32
|
-
saveInstalledPluginVersionForScope,
|
|
33
|
-
} from "../services/plugin-manager.js";
|
|
34
|
-
import {
|
|
35
|
-
checkPluginVersionMismatches,
|
|
36
|
-
formatMismatchWarning,
|
|
37
|
-
} from "../services/plugin-version-check.js";
|
|
38
|
-
import { UpdateCache } from "../services/update-cache.js";
|
|
39
|
-
|
|
40
|
-
export interface PrerunOptions {
|
|
41
|
-
force?: boolean; // Bypass cache and force update check
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const CONTINUITY_PLUGIN_SENTINEL = "tmux-claude-continuity";
|
|
45
|
-
const CONTINUITY_PLUGIN_SCRIPT = path.join(
|
|
46
|
-
os.homedir(),
|
|
47
|
-
".tmux",
|
|
48
|
-
"plugins",
|
|
49
|
-
"tmux-claude-continuity",
|
|
50
|
-
"scripts",
|
|
51
|
-
"on_session_start.sh",
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Ensure tmux-claude-continuity Claude Code hooks are present in global settings.
|
|
56
|
-
* If the tmux plugin is installed but the hooks are missing, they are appended.
|
|
57
|
-
* Returns a description of what was added, or null if nothing changed.
|
|
58
|
-
*/
|
|
59
|
-
async function ensureTmuxContinuityHooks(): Promise<string | null> {
|
|
60
|
-
// Plugin not installed — nothing to do
|
|
61
|
-
if (!(await fs.pathExists(CONTINUITY_PLUGIN_SCRIPT))) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const settings = await readGlobalSettings();
|
|
66
|
-
|
|
67
|
-
// Check if hooks are already configured by looking for the sentinel string
|
|
68
|
-
const existingHooks = settings.hooks ?? {};
|
|
69
|
-
for (const groups of Object.values(existingHooks)) {
|
|
70
|
-
for (const group of groups) {
|
|
71
|
-
for (const hook of group.hooks) {
|
|
72
|
-
if (hook.command.includes(CONTINUITY_PLUGIN_SENTINEL)) {
|
|
73
|
-
return null; // Already configured
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Append hooks, preserving any existing entries in SessionStart and Stop
|
|
80
|
-
const sessionStartGroups = existingHooks.SessionStart ?? [];
|
|
81
|
-
const stopGroups = existingHooks.Stop ?? [];
|
|
82
|
-
|
|
83
|
-
sessionStartGroups.push({
|
|
84
|
-
hooks: [
|
|
85
|
-
{
|
|
86
|
-
type: "command",
|
|
87
|
-
command:
|
|
88
|
-
"bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_session_start.sh",
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
stopGroups.push({
|
|
94
|
-
hooks: [
|
|
95
|
-
{
|
|
96
|
-
type: "command",
|
|
97
|
-
command:
|
|
98
|
-
"bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_stop.sh",
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
settings.hooks = {
|
|
104
|
-
...existingHooks,
|
|
105
|
-
SessionStart: sessionStartGroups,
|
|
106
|
-
Stop: stopGroups,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
await writeGlobalSettings(settings);
|
|
110
|
-
return "SessionStart + Stop hooks";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Prerun orchestration: Check for updates, apply them, then run claude
|
|
115
|
-
* @param claudeArgs - Arguments to pass to claude CLI
|
|
116
|
-
* @param options - Prerun options (force, etc.)
|
|
117
|
-
* @returns Exit code from claude process
|
|
118
|
-
*/
|
|
119
|
-
export async function prerunClaude(
|
|
120
|
-
claudeArgs: string[],
|
|
121
|
-
options: PrerunOptions = {},
|
|
122
|
-
): Promise<number> {
|
|
123
|
-
const cache = new UpdateCache();
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
// STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
|
|
127
|
-
const migration = await migrateMarketplaceRename();
|
|
128
|
-
const migTotal =
|
|
129
|
-
migration.projectMigrated +
|
|
130
|
-
migration.globalMigrated +
|
|
131
|
-
migration.localMigrated +
|
|
132
|
-
migration.registryMigrated +
|
|
133
|
-
(migration.knownMarketplacesMigrated ? 1 : 0);
|
|
134
|
-
if (migTotal > 0) {
|
|
135
|
-
console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// STEP 0.5: Auto-add missing marketplaces
|
|
139
|
-
// When plugins reference a marketplace that's not installed locally
|
|
140
|
-
// (e.g., settings synced from another machine or committed by a
|
|
141
|
-
// teammate in .claude/settings.json), add it automatically. Pass
|
|
142
|
-
// process.cwd() so project and local enabledPlugins are scanned —
|
|
143
|
-
// otherwise a freshly cloned repo whose only @magus references live
|
|
144
|
-
// in project settings looks "clean" from the global-scope view.
|
|
145
|
-
if (await isClaudeAvailable()) {
|
|
146
|
-
const addedMarketplaces = await autoAddMissingMarketplaces(process.cwd());
|
|
147
|
-
if (addedMarketplaces.length > 0) {
|
|
148
|
-
console.log(
|
|
149
|
-
`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// STEP 0.55: Clean up extraKnownMarketplaces entries that are now in known_marketplaces.json
|
|
155
|
-
// extraKnownMarketplaces was the old install mechanism; the official way is known_marketplaces.json
|
|
156
|
-
try {
|
|
157
|
-
const cleaned = await cleanupExtraKnownMarketplaces();
|
|
158
|
-
if (cleaned.length > 0) {
|
|
159
|
-
console.log(
|
|
160
|
-
`✓ Cleaned up legacy extraKnownMarketplaces: ${cleaned.join(", ")}`,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
} catch {
|
|
164
|
-
// Non-fatal: cleanup is best-effort
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// STEP 0.6: Ensure tmux-claude-continuity hooks are configured
|
|
168
|
-
const addedHooks = await ensureTmuxContinuityHooks();
|
|
169
|
-
if (addedHooks) {
|
|
170
|
-
console.log(
|
|
171
|
-
"✓ Added tmux-claude-continuity hooks to ~/.claude/settings.json",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// STEP 0.7: Gap-fill installedPluginVersions across scopes
|
|
176
|
-
// When a plugin is updated at project/local scope, global may lag behind.
|
|
177
|
-
// Sync the highest version from any scope up to global so Claude Code
|
|
178
|
-
// resolves the latest cached plugin paths at session startup.
|
|
179
|
-
try {
|
|
180
|
-
const gapFilled = await gapFillInstalledPluginVersions(process.cwd());
|
|
181
|
-
if (gapFilled.length > 0) {
|
|
182
|
-
console.log(
|
|
183
|
-
`✓ Synced ${gapFilled.length} plugin version(s) to global:`,
|
|
184
|
-
);
|
|
185
|
-
for (const { pluginId, oldVersion, newVersion } of gapFilled) {
|
|
186
|
-
console.log(` - ${pluginId}: ${oldVersion} → ${newVersion}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
} catch {
|
|
190
|
-
// Non-fatal: gap-fill is best-effort
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// STEP 0.8: Detect plugin version mismatches caused by the [0] index bug
|
|
194
|
-
// Claude Code's pluginLoader.ts takes entries[0] regardless of project,
|
|
195
|
-
// so if another project installed a different version first, this project
|
|
196
|
-
// will silently load the wrong version.
|
|
197
|
-
try {
|
|
198
|
-
const cwd = process.cwd();
|
|
199
|
-
const mismatches = await checkPluginVersionMismatches(cwd);
|
|
200
|
-
if (mismatches.length > 0) {
|
|
201
|
-
console.warn("");
|
|
202
|
-
console.warn(formatMismatchWarning(mismatches));
|
|
203
|
-
console.warn("");
|
|
204
|
-
}
|
|
205
|
-
} catch {
|
|
206
|
-
// Non-fatal: mismatch detection is best-effort
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// STEP 0.9: Detect gitignore manifest violations. Warn-only here — the
|
|
210
|
-
// fix dialog lives in the TUI Gitignore tab, mirroring the plugin
|
|
211
|
-
// version-mismatch flow (since destructive fixes need user confirmation).
|
|
212
|
-
try {
|
|
213
|
-
const cwd = process.cwd();
|
|
214
|
-
const result = await checkGitignore(cwd);
|
|
215
|
-
const warning = formatGitignoreWarning(result);
|
|
216
|
-
if (warning) {
|
|
217
|
-
console.warn("");
|
|
218
|
-
console.warn(warning);
|
|
219
|
-
console.warn("");
|
|
220
|
-
}
|
|
221
|
-
} catch {
|
|
222
|
-
// Non-fatal: gitignore detection is best-effort
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// STEP 1: Check if we should update (time-based cache, or forced)
|
|
226
|
-
const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
|
|
227
|
-
|
|
228
|
-
if (options.force) {
|
|
229
|
-
console.log("⟳ Forcing plugin update check...");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (shouldUpdate) {
|
|
233
|
-
// STEP 1.5: Recover marketplace settings (enable autoUpdate, remove stale entries)
|
|
234
|
-
const recovery = await recoverMarketplaceSettings();
|
|
235
|
-
if (recovery.enabledAutoUpdate.length > 0) {
|
|
236
|
-
console.log(
|
|
237
|
-
`✓ Enabled auto-update for: ${recovery.enabledAutoUpdate.join(", ")}`,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
if (recovery.removed.length > 0) {
|
|
241
|
-
console.log(
|
|
242
|
-
`✓ Removed stale marketplaces: ${recovery.removed.join(", ")}`,
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
if (recovery.reregistered.length > 0) {
|
|
246
|
-
console.log(
|
|
247
|
-
`✓ Re-registered as GitHub source: ${recovery.reregistered.join(", ")}`,
|
|
248
|
-
);
|
|
249
|
-
// Re-registration only rewrites known_marketplaces.json (directory →
|
|
250
|
-
// github source); it does NOT create the on-disk clone. Clone any whose
|
|
251
|
-
// directory is missing, using the NON-DESTRUCTIVE `marketplace add` —
|
|
252
|
-
// never `marketplace update`, which deletes the directory before
|
|
253
|
-
// re-cloning and can leave it permanently gone on failure (see
|
|
254
|
-
// marketplace-refresh.ts / marketplace-sync.ts). Clones that already
|
|
255
|
-
// exist are left to STEP 1.7's safe `git pull --ff-only`.
|
|
256
|
-
const cliAvailable = await isClaudeAvailable();
|
|
257
|
-
if (cliAvailable) {
|
|
258
|
-
const { addMarketplace } = await import("../services/claude-cli.js");
|
|
259
|
-
const { defaultMarketplaces } = await import(
|
|
260
|
-
"../data/marketplaces.js"
|
|
261
|
-
);
|
|
262
|
-
const marketplacesDir = path.join(
|
|
263
|
-
os.homedir(),
|
|
264
|
-
".claude",
|
|
265
|
-
"plugins",
|
|
266
|
-
"marketplaces",
|
|
267
|
-
);
|
|
268
|
-
for (const mpName of recovery.reregistered) {
|
|
269
|
-
// Already on disk → STEP 1.7 fast-forwards it; nothing to clone.
|
|
270
|
-
if (await fs.pathExists(path.join(marketplacesDir, mpName))) {
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
const repo = defaultMarketplaces.find((m) => m.name === mpName)
|
|
274
|
-
?.source.repo;
|
|
275
|
-
if (!repo) continue;
|
|
276
|
-
try {
|
|
277
|
-
await addMarketplace(repo);
|
|
278
|
-
console.log(`✓ Cloned re-registered marketplace: ${mpName}`);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
console.warn(
|
|
281
|
-
`⚠ Failed to clone marketplace ${mpName}:`,
|
|
282
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// STEP 1.7: Fast-forward the on-disk clones of every registered
|
|
290
|
-
// marketplace. Recovery above only *clones* re-registered ones that were
|
|
291
|
-
// missing; nothing else advances an existing healthy clone, so updated
|
|
292
|
-
// plugin setup metadata — e.g. a pinned dependency version — is never seen
|
|
293
|
-
// even though the fetched plugin list is current. Non-destructive
|
|
294
|
-
// `git pull --ff-only`: a failed pull leaves the clone stale, never gone.
|
|
295
|
-
const mpRefresh = await refreshRegisteredMarketplaces();
|
|
296
|
-
if (mpRefresh.refreshed.length > 0) {
|
|
297
|
-
console.log(
|
|
298
|
-
`✓ Refreshed marketplaces: ${mpRefresh.refreshed.join(", ")}`,
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
if (mpRefresh.autoUpdateDisabled.length > 0) {
|
|
302
|
-
// Silence here is what let a clone sit 11 days behind: the catalog
|
|
303
|
-
// stops moving, so every plugin from that marketplace reads as up
|
|
304
|
-
// to date and no update is ever offered.
|
|
305
|
-
for (const name of mpRefresh.autoUpdateDisabled) {
|
|
306
|
-
console.log(
|
|
307
|
-
`⚠ ${name}: auto-update disabled — its plugin catalog will not refresh, so updates stay hidden.`,
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
console.log(
|
|
311
|
-
` Re-enable with: claude plugin marketplace update ${mpRefresh.autoUpdateDisabled[0]} (or set autoUpdate:true in ~/.claude/plugins/known_marketplaces.json)`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
for (const name of mpRefresh.failed) {
|
|
315
|
-
console.warn(`⚠ Failed to refresh marketplace: ${name}`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// STEP 2: Clear cache to force fresh plugin info
|
|
319
|
-
clearMarketplaceCache();
|
|
320
|
-
// The marketplace refresh above may have moved each clone's HEAD, and
|
|
321
|
-
// drift answers are relative to HEAD.
|
|
322
|
-
clearContentDriftCache();
|
|
323
|
-
|
|
324
|
-
// STEP 3: Get updated plugin info (to detect versions)
|
|
325
|
-
const plugins = await getAvailablePlugins();
|
|
326
|
-
|
|
327
|
-
// STEP 4: Auto-update enabled plugins via claude CLI
|
|
328
|
-
const autoUpdatedPlugins: Array<{
|
|
329
|
-
pluginId: string;
|
|
330
|
-
oldVersion: string;
|
|
331
|
-
newVersion: string;
|
|
332
|
-
/** Scopes actually updated — never assume "user". */
|
|
333
|
-
scopes: ReadonlyArray<PluginScope>;
|
|
334
|
-
}> = [];
|
|
335
|
-
/** Plugins whose files drifted under an unchanged version. */
|
|
336
|
-
const repairedPlugins: Array<{
|
|
337
|
-
pluginId: string;
|
|
338
|
-
scope: PluginScope;
|
|
339
|
-
}> = [];
|
|
340
|
-
|
|
341
|
-
const cliAvailable = await isClaudeAvailable();
|
|
342
|
-
|
|
343
|
-
for (const plugin of plugins) {
|
|
344
|
-
// Only update if:
|
|
345
|
-
// 1. Plugin is enabled
|
|
346
|
-
// 2. Plugin has an update available
|
|
347
|
-
// 3. Plugin has both installedVersion and version (newVersion)
|
|
348
|
-
// A content-stale plugin has NO version bump to detect — its files
|
|
349
|
-
// changed under an unchanged version — so it must be repaired
|
|
350
|
-
// (uninstall+install) rather than updated. Auto-fixing broken state
|
|
351
|
-
// without human interaction is claudeup's job; leaving a plugin
|
|
352
|
-
// silently running deleted skills is exactly the state it exists
|
|
353
|
-
// to prevent.
|
|
354
|
-
if (
|
|
355
|
-
plugin.enabled &&
|
|
356
|
-
!plugin.hasUpdate &&
|
|
357
|
-
plugin.contentStale &&
|
|
358
|
-
plugin.installedVersion
|
|
359
|
-
) {
|
|
360
|
-
if (!cliAvailable) continue;
|
|
361
|
-
const staleScopes = (
|
|
362
|
-
[
|
|
363
|
-
["user", plugin.userScope],
|
|
364
|
-
["project", plugin.projectScope],
|
|
365
|
-
["local", plugin.localScope],
|
|
366
|
-
] as const
|
|
367
|
-
)
|
|
368
|
-
.filter(([, status]) => !!status?.version)
|
|
369
|
-
.map(([scope]) => scope);
|
|
370
|
-
|
|
371
|
-
for (const scope of staleScopes) {
|
|
372
|
-
try {
|
|
373
|
-
await repairPlugin(plugin.id, scope, process.cwd());
|
|
374
|
-
repairedPlugins.push({ pluginId: plugin.id, scope });
|
|
375
|
-
} catch (error) {
|
|
376
|
-
console.warn(
|
|
377
|
-
`⚠ Failed to repair ${plugin.id} (${scope}):`,
|
|
378
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
plugin.enabled &&
|
|
387
|
-
plugin.hasUpdate &&
|
|
388
|
-
plugin.installedVersion &&
|
|
389
|
-
plugin.version
|
|
390
|
-
) {
|
|
391
|
-
try {
|
|
392
|
-
if (!cliAvailable) {
|
|
393
|
-
// CLI unavailable — skip update entirely, don't write phantom state
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Update every scope that actually holds an outdated install.
|
|
398
|
-
//
|
|
399
|
-
// This used to be a hardcoded `updatePlugin(plugin.id, "user")`,
|
|
400
|
-
// which was wrong for almost every real install: `hasUpdate` is
|
|
401
|
-
// computed from the CURRENT PROJECT's resolved version, while
|
|
402
|
-
// installs live per project in installed_plugins.json. On this
|
|
403
|
-
// machine 495 of 496 magus installs are project-scoped, so the
|
|
404
|
-
// prerunner detected project drift, "fixed" it at user scope,
|
|
405
|
-
// reported success — and left the project row untouched. Next run
|
|
406
|
-
// it detected the same drift again. The update never converged,
|
|
407
|
-
// which is what "auto-update ran but an update is still available"
|
|
408
|
-
// actually was.
|
|
409
|
-
const outdatedScopes = (
|
|
410
|
-
[
|
|
411
|
-
["user", plugin.userScope],
|
|
412
|
-
["project", plugin.projectScope],
|
|
413
|
-
["local", plugin.localScope],
|
|
414
|
-
] as const
|
|
415
|
-
)
|
|
416
|
-
.filter(
|
|
417
|
-
([, status]) =>
|
|
418
|
-
!!status?.version &&
|
|
419
|
-
compareVersions(plugin.version, status.version) > 0,
|
|
420
|
-
)
|
|
421
|
-
.map(([scope]) => scope);
|
|
422
|
-
|
|
423
|
-
if (outdatedScopes.length === 0) continue;
|
|
424
|
-
|
|
425
|
-
for (const scope of outdatedScopes) {
|
|
426
|
-
await updatePlugin(plugin.id, scope);
|
|
427
|
-
// The CLI does not maintain installedPluginVersions, so keep
|
|
428
|
-
// claudeup's own copy in step for the scope we just touched.
|
|
429
|
-
await saveInstalledPluginVersionForScope(
|
|
430
|
-
plugin.id,
|
|
431
|
-
plugin.version,
|
|
432
|
-
scope,
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
autoUpdatedPlugins.push({
|
|
437
|
-
pluginId: plugin.id,
|
|
438
|
-
oldVersion: plugin.installedVersion,
|
|
439
|
-
newVersion: plugin.version,
|
|
440
|
-
scopes: outdatedScopes,
|
|
441
|
-
});
|
|
442
|
-
} catch (error) {
|
|
443
|
-
// Non-fatal: Log warning and continue
|
|
444
|
-
console.warn(
|
|
445
|
-
`⚠ Failed to auto-update ${plugin.id}:`,
|
|
446
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// STEP 5: Save cache
|
|
453
|
-
await cache.saveCheck({
|
|
454
|
-
lastUpdateCheck: new Date().toISOString(),
|
|
455
|
-
lastUpdateResult: {
|
|
456
|
-
updated: [],
|
|
457
|
-
failed: [],
|
|
458
|
-
autoUpdated: autoUpdatedPlugins,
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// STEP 6: Display auto-update summary
|
|
463
|
-
if (autoUpdatedPlugins.length > 0) {
|
|
464
|
-
console.log(`✓ Auto-updated ${autoUpdatedPlugins.length} plugin(s):`);
|
|
465
|
-
for (const {
|
|
466
|
-
pluginId,
|
|
467
|
-
oldVersion,
|
|
468
|
-
newVersion,
|
|
469
|
-
scopes,
|
|
470
|
-
} of autoUpdatedPlugins) {
|
|
471
|
-
// Name the scope. "Auto-updated" that silently touched only user
|
|
472
|
-
// scope, while the project stayed behind, is exactly how this became
|
|
473
|
-
// untrustworthy.
|
|
474
|
-
console.log(
|
|
475
|
-
` - ${pluginId}: ${oldVersion} → ${newVersion} (${scopes.join(", ")})`,
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (repairedPlugins.length > 0) {
|
|
481
|
-
console.log(
|
|
482
|
-
`✓ Repaired ${repairedPlugins.length} plugin(s) whose files changed without a version bump:`,
|
|
483
|
-
);
|
|
484
|
-
for (const { pluginId, scope } of repairedPlugins) {
|
|
485
|
-
console.log(` - ${pluginId} (${scope})`);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
// If !shouldUpdate → silent (no output)
|
|
490
|
-
} catch (error) {
|
|
491
|
-
// Non-fatal errors - warn and continue
|
|
492
|
-
console.warn(
|
|
493
|
-
"Warning: Plugin update check failed:",
|
|
494
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// STEP 8: Always run claude (even if update failed)
|
|
499
|
-
return runClaude(claudeArgs);
|
|
500
|
-
}
|