claudeup 4.42.0 → 5.0.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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Manifest bootstrap — the "this repo has no profiles yet" path.
3
+ *
4
+ * `install` and `update` both require `.claude/profiles.json`. Rather than
5
+ * giving each a manifest-free fallback (two behaviours to keep in step, and a
6
+ * repo that never gets a committed manifest), a repo without one is offered
7
+ * adoption: claudeup writes the manifest that describes the project as it
8
+ * stands, then the original command proceeds against it normally.
9
+ */
10
+
11
+ import {
12
+ getManifestPath,
13
+ readManifest,
14
+ writeManifest,
15
+ } from "../services/manifest.js";
16
+ import {
17
+ type AdoptResult,
18
+ adoptCurrentProject,
19
+ } from "../services/profile-adopt.js";
20
+ import type { ProfileManifest } from "../types/index.js";
21
+ import { confirm } from "./prompt.js";
22
+
23
+ /** Print what adoption would capture, so the user can judge it before writing. */
24
+ function printAdoptionPlan(result: AdoptResult): void {
25
+ console.log(
26
+ "\nThis project has no .claude/profiles.json — claudeup can create one from its current setup.\n",
27
+ );
28
+ console.log(` profile ${result.profileId} (${result.entry.name})`);
29
+ console.log(
30
+ ` plugins ${result.counts.plugins} enabled, adopted as "latest"`,
31
+ );
32
+ if (result.source === "user") {
33
+ // Never let this be invisible: the generated file is meant to be committed,
34
+ // and these came from ~/.claude/settings.json — this machine's personal
35
+ // global set, not anything this repo chose.
36
+ console.log(
37
+ " ⚠ read from ~/.claude/settings.json (user scope) — this project",
38
+ );
39
+ console.log(
40
+ " enables none of its own. Review before committing.",
41
+ );
42
+ }
43
+ if (result.counts.mcpServers > 0) {
44
+ console.log(` mcp servers ${result.counts.mcpServers} from .mcp.json`);
45
+ }
46
+ if (result.counts.settingsKeys > 0) {
47
+ console.log(
48
+ ` settings ${result.counts.settingsKeys} keys carried over`,
49
+ );
50
+ }
51
+ for (const id of result.skipped) {
52
+ console.log(
53
+ ` ⚠ skipped ${id} — no marketplace in its id, not reproducible`,
54
+ );
55
+ }
56
+ for (const hit of result.machinePaths) {
57
+ console.log(` ⚠ machine path ${hit}`);
58
+ }
59
+ if (result.machinePaths.length > 0) {
60
+ console.log(
61
+ " Those name a directory on THIS machine and will not resolve on another.",
62
+ );
63
+ }
64
+ console.log(
65
+ "\nCommit the file to share this setup with the team; edit it to pin versions.",
66
+ );
67
+ }
68
+
69
+ export interface EnsureManifestOptions {
70
+ /**
71
+ * Skip confirmation prompts. Deliberately does NOT authorize adoption —
72
+ * `--yes` means "I already know what this will do", and nobody passing it to
73
+ * an install has decided what their team's committed manifest should contain.
74
+ */
75
+ yes?: boolean;
76
+ /** Read-only mode: never write, just report that adoption is needed. */
77
+ check?: boolean;
78
+ /**
79
+ * Adopt without prompting. Set only by `claudeup profile init`, where
80
+ * creating the manifest IS the command the user asked for.
81
+ */
82
+ allowAdopt?: boolean;
83
+ }
84
+
85
+ /**
86
+ * Return the project's manifest, creating one by adoption if it has none.
87
+ *
88
+ * Resolves to null when there is no manifest and none was created — the caller
89
+ * should exit non-zero. Anything it prints has already explained why.
90
+ */
91
+ export async function ensureManifest(
92
+ projectPath: string,
93
+ opts: EnsureManifestOptions = {},
94
+ ): Promise<ProfileManifest | null> {
95
+ const manifest = await readManifest(projectPath);
96
+ if (Object.keys(manifest.profiles).length > 0) return manifest;
97
+
98
+ const result = await adoptCurrentProject(projectPath);
99
+
100
+ if (result.counts.plugins === 0) {
101
+ console.error(
102
+ `No .claude/profiles.json in ${projectPath}, and no enabled plugins to adopt one from.`,
103
+ );
104
+ console.error(
105
+ "Open claudeup and select the plugins this project needs, then re-run.",
106
+ );
107
+ return null;
108
+ }
109
+
110
+ printAdoptionPlan(result);
111
+
112
+ if (opts.check) {
113
+ console.error(
114
+ "\n--check is read-only and will not create the manifest. Run `claudeup install` once to adopt.",
115
+ );
116
+ return null;
117
+ }
118
+
119
+ // `--yes` alone is NOT consent to author this file. It means "do not ask me
120
+ // about the thing I invoked", and someone running `claudeup update --yes` in
121
+ // CI invoked an update, not the creation of their team's committed contract —
122
+ // which this would otherwise write unreviewed, possibly from the machine's
123
+ // user-scope plugin set. Creating it needs either a human at the prompt or
124
+ // the command whose whole purpose is to create it.
125
+ if (opts.allowAdopt) {
126
+ // fall through to the write
127
+ } else if (!process.stdin.isTTY) {
128
+ console.error(
129
+ "\nRefusing to create .claude/profiles.json without a human: run `claudeup profile init` to create it deliberately.",
130
+ );
131
+ return null;
132
+ } else if (!(await confirm("\nCreate .claude/profiles.json?"))) {
133
+ console.log("Aborted — nothing written.");
134
+ return null;
135
+ }
136
+
137
+ await writeManifest(result.manifest, projectPath);
138
+ console.log(`✓ Wrote ${getManifestPath(projectPath)}\n`);
139
+ return result.manifest;
140
+ }
@@ -13,46 +13,43 @@
13
13
  */
14
14
 
15
15
  import path from "node:path";
16
- import { createInterface } from "node:readline/promises";
17
- import type {
18
- ProfileSkillRef,
19
- ResolvedBin,
20
- ResolvedClosure,
21
- SkillInfo,
22
- } from "../types/index.js";
23
- import { readManifest, validateManifest } from "../services/manifest.js";
24
- import { resolveAllProfiles, resolveProfile } from "../services/resolver.js";
16
+ import { installPlugin, isClaudeAvailable } from "../services/claude-cli.js";
17
+ import {
18
+ readInstalledPluginsRegistry,
19
+ readLocalSettings,
20
+ writeLocalSettings,
21
+ } from "../services/claude-settings.js";
22
+ import { ensureGitignoreEntries } from "../services/conventions-manager.js";
25
23
  import {
26
24
  computeEnvStatus,
27
25
  computeVersionDrift,
28
26
  summarizeClosure,
29
27
  } from "../services/install-plan.js";
30
- import {
31
- binInstallCommand,
32
- detectToolchains,
33
- TOOLCHAIN_BOOTSTRAP,
34
- } from "../services/toolchain.js";
28
+ import { validateManifest } from "../services/manifest.js";
29
+ import { registerClosureMarketplaces } from "../services/marketplace-sync.js";
35
30
  import { materializeProfile } from "../services/profile-materializer.js";
31
+ import { detectProfileDrift } from "../services/profile-sync.js";
32
+ import { resolveAllProfiles, resolveProfile } from "../services/resolver.js";
33
+ import { installSkill } from "../services/skills-manager.js";
36
34
  import {
35
+ PROFILE_GITIGNORE_ENTRIES,
37
36
  activateProfile,
38
37
  activeProfile,
39
- PROFILE_GITIGNORE_ENTRIES,
40
38
  } from "../services/symlink-manager.js";
41
- import { ensureGitignoreEntries } from "../services/conventions-manager.js";
42
- import { detectProfileDrift } from "../services/profile-sync.js";
43
- import { resolveExecutable } from "../utils/command-utils.js";
44
39
  import {
45
- addMarketplace,
46
- installPlugin,
47
- isClaudeAvailable,
48
- } from "../services/claude-cli.js";
49
- import {
50
- isMarketplaceRegistered,
51
- readInstalledPluginsRegistry,
52
- readLocalSettings,
53
- writeLocalSettings,
54
- } from "../services/claude-settings.js";
55
- import { installSkill } from "../services/skills-manager.js";
40
+ TOOLCHAIN_BOOTSTRAP,
41
+ binInstallCommand,
42
+ detectToolchains,
43
+ } from "../services/toolchain.js";
44
+ import type {
45
+ ProfileSkillRef,
46
+ ResolvedBin,
47
+ ResolvedClosure,
48
+ SkillInfo,
49
+ } from "../types/index.js";
50
+ import { resolveExecutable } from "../utils/command-utils.js";
51
+ import { ensureManifest } from "./bootstrap.js";
52
+ import { confirm, promptValue, runShell } from "./prompt.js";
56
53
 
57
54
  interface InstallFlags {
58
55
  check: boolean;
@@ -70,35 +67,6 @@ function parseArgs(args: string[]): InstallFlags {
70
67
  };
71
68
  }
72
69
 
73
- async function confirm(question: string): Promise<boolean> {
74
- const rl = createInterface({ input: process.stdin, output: process.stdout });
75
- try {
76
- const answer = (await rl.question(`${question} [Y/n] `)).trim().toLowerCase();
77
- return answer === "" || answer === "y" || answer === "yes";
78
- } finally {
79
- rl.close();
80
- }
81
- }
82
-
83
- async function promptValue(question: string): Promise<string> {
84
- const rl = createInterface({ input: process.stdin, output: process.stdout });
85
- try {
86
- return (await rl.question(question)).trim();
87
- } finally {
88
- rl.close();
89
- }
90
- }
91
-
92
- /** Run a shell command, streaming output; resolve true on exit 0. */
93
- async function runShell(command: string): Promise<boolean> {
94
- const { spawn } = await import("node:child_process");
95
- return new Promise((resolve) => {
96
- const child = spawn(command, { stdio: "inherit", shell: true });
97
- child.on("exit", (code) => resolve(code === 0));
98
- child.on("error", () => resolve(false));
99
- });
100
- }
101
-
102
70
  // ── --check: read-only drift gate ───────────────────────────────────────────
103
71
 
104
72
  async function runCheck(
@@ -113,7 +81,9 @@ async function runCheck(
113
81
  }
114
82
  console.log("Plugin version drift:");
115
83
  for (const d of drift) {
116
- console.log(` ✗ ${d.pluginId} pinned ${d.pinned}, installed ${d.installed ?? "none"}`);
84
+ console.log(
85
+ ` ✗ ${d.pluginId} pinned ${d.pinned}, installed ${d.installed ?? "none"}`,
86
+ );
117
87
  }
118
88
  if (strict) {
119
89
  console.error("\nstrictVersions is on — failing.");
@@ -125,7 +95,10 @@ async function runCheck(
125
95
 
126
96
  // ── install steps ───────────────────────────────────────────────────────────
127
97
 
128
- async function ensureToolchains(closure: ResolvedClosure, yes: boolean): Promise<void> {
98
+ async function ensureToolchains(
99
+ closure: ResolvedClosure,
100
+ yes: boolean,
101
+ ): Promise<void> {
129
102
  // Only the binaries actually missing can need a toolchain. Checking all of
130
103
  // them warns "pip isn't installed" on a machine whose pip-provided binaries
131
104
  // are already on PATH — an alarming message about work that will not happen.
@@ -145,7 +118,11 @@ async function ensureToolchains(closure: ResolvedClosure, yes: boolean): Promise
145
118
  );
146
119
  continue;
147
120
  }
148
- const ok = yes || (await confirm(`${tc.name} is missing (needed by ${tc.requiredBy.join(", ")}). Install it?`));
121
+ const ok =
122
+ yes ||
123
+ (await confirm(
124
+ `${tc.name} is missing (needed by ${tc.requiredBy.join(", ")}). Install it?`,
125
+ ));
149
126
  if (ok) {
150
127
  console.log(`Installing ${tc.name}…`);
151
128
  if (!(await runShell(bootstrap))) {
@@ -155,20 +132,6 @@ async function ensureToolchains(closure: ResolvedClosure, yes: boolean): Promise
155
132
  }
156
133
  }
157
134
 
158
- async function registerMarketplaces(closure: ResolvedClosure): Promise<void> {
159
- for (const [name, ref] of Object.entries(closure.marketplaces)) {
160
- try {
161
- if (await isMarketplaceRegistered(name)) continue;
162
- const source = ref.source === "github" ? ref.repo : ref.path;
163
- if (!source) continue;
164
- console.log(`+ marketplace ${name} (${source})`);
165
- await addMarketplace(source);
166
- } catch (e) {
167
- console.warn(`⚠ marketplace ${name}: ${(e as Error).message}`);
168
- }
169
- }
170
- }
171
-
172
135
  /**
173
136
  * Install the closure's plugins at PROJECT scope.
174
137
  *
@@ -212,7 +175,11 @@ function toSkillInfo(ref: ProfileSkillRef): SkillInfo {
212
175
  return {
213
176
  id: `${ref.repo}/${ref.path}`,
214
177
  name: ref.name,
215
- source: { label: ref.repo, repo: ref.repo, skillsPath: path.dirname(ref.path) },
178
+ source: {
179
+ label: ref.repo,
180
+ repo: ref.repo,
181
+ skillsPath: path.dirname(ref.path),
182
+ },
216
183
  repoPath: ref.path,
217
184
  gitBlobSha: "",
218
185
  frontmatter: null,
@@ -246,8 +213,11 @@ async function collectEnv(
246
213
 
247
214
  console.log("\nMissing required environment variables:");
248
215
  if (yes) {
249
- for (const name of status.missingRequired) console.log(` ! ${name} (unset)`);
250
- console.log("Run without --yes to enter values, or set them in .claude/settings.local.json.");
216
+ for (const name of status.missingRequired)
217
+ console.log(` ! ${name} (unset)`);
218
+ console.log(
219
+ "Run without --yes to enter values, or set them in .claude/settings.local.json.",
220
+ );
251
221
  return;
252
222
  }
253
223
 
@@ -269,7 +239,14 @@ export async function runInstallCommand(
269
239
  ): Promise<number> {
270
240
  const flags = parseArgs(args);
271
241
 
272
- const manifest = await readManifest(projectPath);
242
+ // A repo with no manifest is offered adoption rather than an error — see
243
+ // cli/bootstrap.ts for why there is no manifest-free install path.
244
+ const manifest = await ensureManifest(projectPath, {
245
+ yes: flags.yes,
246
+ check: flags.check,
247
+ });
248
+ if (!manifest) return 1;
249
+
273
250
  const errors = validateManifest(manifest);
274
251
  if (errors.length > 0) {
275
252
  console.error("Invalid .claude/profiles.json:");
@@ -318,7 +295,9 @@ export async function runInstallCommand(
318
295
  console.log(`\nPlan for ${flags.profile ?? "all profiles"}:`);
319
296
  for (const line of summarizeClosure(closure)) console.log(` ${line}`);
320
297
  if (closure.conflicts?.length) {
321
- console.log("\n⚠ Version conflicts across profiles (resolved to the higher pin):");
298
+ console.log(
299
+ "\n⚠ Version conflicts across profiles (resolved to the higher pin):",
300
+ );
322
301
  for (const c of closure.conflicts) console.log(` ${c}`);
323
302
  }
324
303
  console.log();
@@ -329,12 +308,14 @@ export async function runInstallCommand(
329
308
  }
330
309
 
331
310
  if (!(await isClaudeAvailable())) {
332
- console.error("claude CLI not found on PATH — cannot install plugins/marketplaces.");
311
+ console.error(
312
+ "claude CLI not found on PATH — cannot install plugins/marketplaces.",
313
+ );
333
314
  return 1;
334
315
  }
335
316
 
336
317
  await ensureToolchains(closure, flags.yes);
337
- await registerMarketplaces(closure);
318
+ await registerClosureMarketplaces(closure.marketplaces);
338
319
  await installPlugins(closure);
339
320
  await installBins(closure.bins);
340
321
 
@@ -362,7 +343,8 @@ export async function runInstallCommand(
362
343
  "# claudeup: generated profile build output + active-profile symlinks",
363
344
  );
364
345
 
365
- const activeId = flags.profile ?? (await pickActiveProfile(profileIds, flags.yes));
346
+ const activeId =
347
+ flags.profile ?? (await pickActiveProfile(profileIds, flags.yes));
366
348
  if (activeId) {
367
349
  await activateProfile(activeId, projectPath);
368
350
  console.log(`\n✓ Active profile: ${activeId}`);
@@ -8,19 +8,20 @@
8
8
  */
9
9
 
10
10
  import path from "node:path";
11
- import { readManifest } from "../services/manifest.js";
12
- import { resolveProfile } from "../services/resolver.js";
11
+ import fs from "fs-extra";
12
+ import { ensureGitignoreEntries } from "../services/conventions-manager.js";
13
+ import { checkBinaries, missingBinaries } from "../services/doctor-bins.js";
13
14
  import { summarizeClosure } from "../services/install-plan.js";
15
+ import { getManifestPath, readManifest } from "../services/manifest.js";
16
+ import { syncProfile } from "../services/profile-sync.js";
17
+ import { resolveProfile } from "../services/resolver.js";
14
18
  import {
19
+ PROFILE_GITIGNORE_ENTRIES,
15
20
  activateProfile,
16
21
  activeProfile,
17
22
  profileDir,
18
- PROFILE_GITIGNORE_ENTRIES,
19
23
  } from "../services/symlink-manager.js";
20
- import { ensureGitignoreEntries } from "../services/conventions-manager.js";
21
- import { syncProfile } from "../services/profile-sync.js";
22
- import { checkBinaries, missingBinaries } from "../services/doctor-bins.js";
23
- import fs from "fs-extra";
24
+ import { ensureManifest } from "./bootstrap.js";
24
25
 
25
26
  export async function runProfileCommand(args: string[]): Promise<number> {
26
27
  const [sub, ...rest] = args;
@@ -34,16 +35,46 @@ export async function runProfileCommand(args: string[]): Promise<number> {
34
35
  return showProfile(rest[0], projectPath);
35
36
  case "switch":
36
37
  return switchProfile(rest[0], projectPath);
38
+ case "init":
39
+ return initProfile(
40
+ args.includes("--yes") || args.includes("-y"),
41
+ projectPath,
42
+ );
37
43
  case "sync":
38
44
  return syncActiveProfile(rest[0], projectPath);
39
45
  default:
40
46
  console.error(
41
- `Unknown profile command "${sub}". Use: list | show <name> | switch <name> | sync`,
47
+ `Unknown profile command "${sub}". Use: list | show <name> | switch <name> | init | sync`,
42
48
  );
43
49
  return 1;
44
50
  }
45
51
  }
46
52
 
53
+ /**
54
+ * `profile init` — write a manifest describing this project as it stands.
55
+ *
56
+ * The same adoption `install`/`update` offer, available on its own so a repo
57
+ * can be brought under profile management without installing anything.
58
+ */
59
+ export async function initProfile(
60
+ yes: boolean,
61
+ projectPath: string,
62
+ ): Promise<number> {
63
+ const existing = await readManifest(projectPath);
64
+ if (Object.keys(existing.profiles).length > 0) {
65
+ console.error(
66
+ `${getManifestPath(projectPath)} already defines: ${Object.keys(existing.profiles).join(", ")}`,
67
+ );
68
+ console.error(
69
+ `Use \`claudeup profile sync\` to promote local edits into it.`,
70
+ );
71
+ return 1;
72
+ }
73
+ // `profile init` exists to create the manifest, so it is the one caller that
74
+ // may do so unattended.
75
+ return (await ensureManifest(projectPath, { yes, allowAdopt: yes })) ? 0 : 1;
76
+ }
77
+
47
78
  export async function listProfiles(projectPath: string): Promise<number> {
48
79
  const manifest = await readManifest(projectPath);
49
80
  const ids = Object.keys(manifest.profiles);
@@ -129,7 +160,8 @@ export async function switchProfile(
129
160
  console.log(
130
161
  `\n⚠ ${missing.length} binary dependenc${missing.length === 1 ? "y is" : "ies are"} missing:`,
131
162
  );
132
- for (const m of missing) console.log(` ✗ ${m.name} — run: ${m.installCommand}`);
163
+ for (const m of missing)
164
+ console.log(` ✗ ${m.name} — run: ${m.installCommand}`);
133
165
  console.log(`Run \`claudeup install ${name}\` to install them.`);
134
166
  }
135
167
  } catch {
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Terminal prompting shared by the non-interactive commands.
3
+ *
4
+ * These lived inside cli/install.ts until `update` and the adopt flow needed
5
+ * the same three. One copy, so a fix to (say) the Y/n default reaches every
6
+ * command at once.
7
+ */
8
+
9
+ import { createInterface } from "node:readline/promises";
10
+
11
+ /** Y/n prompt. Empty input means yes. */
12
+ export async function confirm(question: string): Promise<boolean> {
13
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14
+ try {
15
+ const answer = (await rl.question(`${question} [Y/n] `))
16
+ .trim()
17
+ .toLowerCase();
18
+ return answer === "" || answer === "y" || answer === "yes";
19
+ } finally {
20
+ rl.close();
21
+ }
22
+ }
23
+
24
+ /** Free-text prompt, trimmed. */
25
+ export async function promptValue(question: string): Promise<string> {
26
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
27
+ try {
28
+ return (await rl.question(question)).trim();
29
+ } finally {
30
+ rl.close();
31
+ }
32
+ }
33
+
34
+ /** Run a shell command, streaming output; resolve true on exit 0. */
35
+ export async function runShell(command: string): Promise<boolean> {
36
+ const { spawn } = await import("node:child_process");
37
+ return new Promise((resolve) => {
38
+ const child = spawn(command, { stdio: "inherit", shell: true });
39
+ child.on("exit", (code) => resolve(code === 0));
40
+ child.on("error", () => resolve(false));
41
+ });
42
+ }
package/src/cli/router.ts CHANGED
@@ -8,10 +8,11 @@
8
8
 
9
9
  import { checkForUpdates } from "../services/version-check.js";
10
10
  import { runClaudeCommand } from "./claude.js";
11
- import { runUpdateCommand } from "./update.js";
11
+ import { runDoctorCommand } from "./doctor.js";
12
12
  import { runInstallCommand } from "./install.js";
13
13
  import { runProfileCommand } from "./profile.js";
14
- import { runDoctorCommand } from "./doctor.js";
14
+ import { runUpdateCommand } from "./update.js";
15
+ import { runUpgradeCommand } from "./upgrade.js";
15
16
 
16
17
  export interface RouteOutcome {
17
18
  /** A subcommand (or top-level flag) handled the invocation. */
@@ -39,7 +40,7 @@ export async function route(
39
40
  const result = await checkForUpdates();
40
41
  if (result.updateAvailable) {
41
42
  console.log(`\nUpdate available: v${result.latestVersion}`);
42
- console.log("Run: claudeup update");
43
+ console.log("Run: claudeup upgrade");
43
44
  }
44
45
  return HANDLED(0);
45
46
  }
@@ -55,7 +56,9 @@ export async function route(
55
56
  case "claude":
56
57
  return HANDLED(await runClaudeCommand(rest));
57
58
  case "update":
58
- return HANDLED(await runUpdateCommand());
59
+ return HANDLED(await runUpdateCommand(rest));
60
+ case "upgrade":
61
+ return HANDLED(await runUpgradeCommand());
59
62
  case "install":
60
63
  return HANDLED(await runInstallCommand(rest));
61
64
  case "profile":
@@ -81,16 +84,27 @@ Options:
81
84
  -h, --help Show this help message
82
85
  --no-refresh Skip auto-refresh of marketplaces on startup
83
86
 
84
- Team profilesreproduce this repo's Claude Code setup on any machine.
85
- The manifest is .claude/profiles.json, committed. See docs/team-configuration.md.
86
- install [profile] Install the profile's plugins, binaries, skills and env,
87
- then activate it. No argument installs every profile.
87
+ Dependency managementkeep this project's plugins, CLI tools and skills
88
+ installed and current. The manifest is .claude/profiles.json, committed; a repo
89
+ without one is offered adoption on first run. See docs/team-configuration.md.
90
+ install [profile] Install the profile's plugins, binaries, skills and env at
91
+ the manifest's PINNED versions, then activate it.
92
+ No argument installs every profile.
88
93
  --check Report drift only, write nothing (exits 1 in strict mode)
89
94
  --yes, -y Skip the confirmation prompt
90
95
  --force Discard unsynced local edits instead of refusing
96
+ update [profile] Keep the ACTIVE profile current: install whatever it
97
+ declares but this machine lacks, and advance anything
98
+ behind the version its marketplace publishes. Only the
99
+ newest version is installable, so a pin naming an older
100
+ one is reported, not chased.
101
+ --check Report only, write nothing (exits 1 if anything is
102
+ missing or known to be behind)
103
+ --yes, -y Skip the confirmation prompt
91
104
  profile list Show every profile; ● marks the active one
92
105
  profile show <n> Print a profile's fully resolved closure
93
106
  profile switch <n> Repoint the active profile — offline, no reinstall
107
+ profile init Write .claude/profiles.json from this project's setup
94
108
  profile sync Promote local edits back into .claude/profiles.json
95
109
  doctor Check binary deps, profile symlinks, and conventions
96
110
  --fix Apply the repairs it can make itself
@@ -98,7 +112,7 @@ The manifest is .claude/profiles.json, committed. See docs/team-configuration.md
98
112
  Other commands:
99
113
  claude [args...] Check for plugin updates (1h cache), then run claude
100
114
  -f, --force Force update check (bypass 1h cache)
101
- update Update claudeup itself to latest version
115
+ upgrade Update claudeup ITSELF to the latest version
102
116
 
103
117
  Navigation (TUI):
104
118
  [1] Plugins [4] Settings [7] Git State