claudeup 4.0.0 → 4.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -18,8 +18,7 @@ export const cliTools: CliTool[] = [
18
18
  "TUI tool for managing Claude Code plugins, MCPs, and configuration",
19
19
  installCommand: "npm install -g claudeup",
20
20
  checkCommand: "claudeup --version",
21
- website:
22
- "https://github.com/MadAppGang/magus/tree/main/tools/claudeup",
21
+ website: "https://github.com/MadAppGang/magus/tree/main/tools/claudeup",
23
22
  category: "ai-coding",
24
23
  packageManager: "npm",
25
24
  packageName: "claudeup",
@@ -70,7 +70,8 @@ export function getAllMarketplaces(localMarketplaces) {
70
70
  const canonical = deprecatedMarketplaces[name];
71
71
  if (canonical) {
72
72
  // If canonical already in the map or in defaults, skip this entry
73
- if (all.has(canonical) || defaultMarketplaces.some((m) => m.name === canonical)) {
73
+ if (all.has(canonical) ||
74
+ defaultMarketplaces.some((m) => m.name === canonical)) {
74
75
  continue;
75
76
  }
76
77
  }
@@ -85,7 +86,10 @@ export function getAllMarketplaces(localMarketplaces) {
85
86
  name,
86
87
  // Prefer default displayName over stale local clone data
87
88
  displayName: defaultMp?.displayName || local.name || formatMarketplaceName(name),
88
- source: { source: "github", repo: defaultMp?.source.repo || local.gitRepo || "" },
89
+ source: {
90
+ source: "github",
91
+ repo: defaultMp?.source.repo || local.gitRepo || "",
92
+ },
89
93
  description: defaultMp?.description || local.description || "",
90
94
  official: defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
91
95
  featured: defaultMp?.featured,
@@ -85,7 +85,10 @@ export function getAllMarketplaces(
85
85
  const canonical = deprecatedMarketplaces[name];
86
86
  if (canonical) {
87
87
  // If canonical already in the map or in defaults, skip this entry
88
- if (all.has(canonical) || defaultMarketplaces.some((m) => m.name === canonical)) {
88
+ if (
89
+ all.has(canonical) ||
90
+ defaultMarketplaces.some((m) => m.name === canonical)
91
+ ) {
89
92
  continue;
90
93
  }
91
94
  }
@@ -100,8 +103,12 @@ export function getAllMarketplaces(
100
103
  all.set(name, {
101
104
  name,
102
105
  // Prefer default displayName over stale local clone data
103
- displayName: defaultMp?.displayName || local.name || formatMarketplaceName(name),
104
- source: { source: "github" as const, repo: defaultMp?.source.repo || local.gitRepo || "" },
106
+ displayName:
107
+ defaultMp?.displayName || local.name || formatMarketplaceName(name),
108
+ source: {
109
+ source: "github" as const,
110
+ repo: defaultMp?.source.repo || local.gitRepo || "",
111
+ },
105
112
  description: defaultMp?.description || local.description || "",
106
113
  official:
107
114
  defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
@@ -1,9 +1,73 @@
1
1
  export const PREDEFINED_PROFILES = [
2
+ {
3
+ id: "must-have",
4
+ name: "Must Have",
5
+ description: "Essential plugins every Claude Code user should have",
6
+ icon: "★",
7
+ magusPlugins: ["statusline", "multimodel"],
8
+ anthropicPlugins: [
9
+ "claude-code-setup",
10
+ "claude-md-management",
11
+ "code-simplifier",
12
+ "explanatory-output-style",
13
+ "playground",
14
+ "skill-creator",
15
+ ],
16
+ skills: ["Find Skills"],
17
+ settings: {
18
+ effortLevel: "high",
19
+ alwaysThinkingEnabled: true,
20
+ outputStyle: "explanatory",
21
+ env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
22
+ respectGitignore: true,
23
+ enableAllProjectMcpServers: true,
24
+ },
25
+ },
26
+ {
27
+ id: "developer-essentials",
28
+ name: "Developer Essentials",
29
+ description: "Must Have + full dev toolkit: code analysis, browser, terminal, design, review",
30
+ icon: "▶",
31
+ magusPlugins: [
32
+ "statusline",
33
+ "multimodel",
34
+ "code-analysis",
35
+ "dev",
36
+ "browser-use",
37
+ "designer",
38
+ "terminal",
39
+ "kanban",
40
+ ],
41
+ anthropicPlugins: [
42
+ "claude-code-setup",
43
+ "claude-md-management",
44
+ "code-simplifier",
45
+ "explanatory-output-style",
46
+ "playground",
47
+ "skill-creator",
48
+ "code-review",
49
+ "commit-commands",
50
+ "feature-dev",
51
+ ],
52
+ skills: ["Find Skills", "Systematic Debugging"],
53
+ settings: {
54
+ effortLevel: "high",
55
+ alwaysThinkingEnabled: true,
56
+ outputStyle: "explanatory",
57
+ env: {
58
+ CLAUDE_CODE_ENABLE_TASKS: "true",
59
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
60
+ },
61
+ includeGitInstructions: true,
62
+ respectGitignore: true,
63
+ enableAllProjectMcpServers: true,
64
+ },
65
+ },
2
66
  {
3
67
  id: "frontend-pro",
4
68
  name: "Frontend Pro",
5
69
  description: "UI implementation, design fidelity, browser-driven workflows",
6
- icon: "🎨",
70
+ icon: "",
7
71
  magusPlugins: [
8
72
  "dev",
9
73
  "code-analysis",
@@ -30,7 +94,6 @@ export const PREDEFINED_PROFILES = [
30
94
  settings: {
31
95
  effortLevel: "high",
32
96
  alwaysThinkingEnabled: true,
33
- model: "claude-sonnet-4-6",
34
97
  outputStyle: "explanatory",
35
98
  env: {
36
99
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -45,7 +108,7 @@ export const PREDEFINED_PROFILES = [
45
108
  id: "backend-forge",
46
109
  name: "Backend Forge",
47
110
  description: "API development, debugging, code quality, data workflows",
48
- icon: "⚙️",
111
+ icon: "",
49
112
  magusPlugins: [
50
113
  "dev",
51
114
  "code-analysis",
@@ -61,13 +124,11 @@ export const PREDEFINED_PROFILES = [
61
124
  "code-simplifier",
62
125
  "commit-commands",
63
126
  "security-guidance",
64
- "agent-sdk-dev",
65
127
  ],
66
128
  skills: ["Systematic Debugging", "Neon Postgres", "Find Skills"],
67
129
  settings: {
68
130
  effortLevel: "high",
69
131
  alwaysThinkingEnabled: true,
70
- model: "claude-sonnet-4-6",
71
132
  outputStyle: "concise",
72
133
  env: {
73
134
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -82,7 +143,7 @@ export const PREDEFINED_PROFILES = [
82
143
  id: "infra-ops",
83
144
  name: "Infra Ops",
84
145
  description: "Infrastructure, operational debugging, automation, terminal-first",
85
- icon: "🔧",
146
+ icon: "",
86
147
  magusPlugins: [
87
148
  "dev",
88
149
  "code-analysis",
@@ -104,7 +165,6 @@ export const PREDEFINED_PROFILES = [
104
165
  settings: {
105
166
  effortLevel: "high",
106
167
  alwaysThinkingEnabled: true,
107
- model: "claude-opus-4-6",
108
168
  outputStyle: "concise",
109
169
  env: {
110
170
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -119,7 +179,7 @@ export const PREDEFINED_PROFILES = [
119
179
  id: "growth-marketer",
120
180
  name: "Growth Marketer",
121
181
  description: "SEO, website audits, content production, marketing automation",
122
- icon: "📈",
182
+ icon: "",
123
183
  magusPlugins: [
124
184
  "dev",
125
185
  "code-analysis",
@@ -143,8 +203,6 @@ export const PREDEFINED_PROFILES = [
143
203
  ],
144
204
  settings: {
145
205
  effortLevel: "medium",
146
- alwaysThinkingEnabled: false,
147
- model: "claude-sonnet-4-6",
148
206
  outputStyle: "explanatory",
149
207
  env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
150
208
  respectGitignore: true,
@@ -155,7 +213,7 @@ export const PREDEFINED_PROFILES = [
155
213
  id: "team-lead",
156
214
  name: "Team Lead",
157
215
  description: "Planning, code review, coordination, and broad repo visibility",
158
- icon: "👔",
216
+ icon: "",
159
217
  magusPlugins: [
160
218
  "dev",
161
219
  "code-analysis",
@@ -177,7 +235,6 @@ export const PREDEFINED_PROFILES = [
177
235
  settings: {
178
236
  effortLevel: "medium",
179
237
  alwaysThinkingEnabled: true,
180
- model: "claude-sonnet-4-6",
181
238
  outputStyle: "explanatory",
182
239
  env: {
183
240
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -2,7 +2,7 @@ export interface PredefinedProfile {
2
2
  id: string;
3
3
  name: string;
4
4
  description: string;
5
- icon: string; // emoji or symbol
5
+ icon: string;
6
6
  magusPlugins: string[];
7
7
  anthropicPlugins: string[];
8
8
  skills: string[];
@@ -10,11 +10,77 @@ export interface PredefinedProfile {
10
10
  }
11
11
 
12
12
  export const PREDEFINED_PROFILES: PredefinedProfile[] = [
13
+ {
14
+ id: "must-have",
15
+ name: "Must Have",
16
+ description: "Essential plugins every Claude Code user should have",
17
+ icon: "★",
18
+ magusPlugins: ["statusline", "multimodel"],
19
+ anthropicPlugins: [
20
+ "claude-code-setup",
21
+ "claude-md-management",
22
+ "code-simplifier",
23
+ "explanatory-output-style",
24
+ "playground",
25
+ "skill-creator",
26
+ ],
27
+ skills: ["Find Skills"],
28
+ settings: {
29
+ effortLevel: "high",
30
+ alwaysThinkingEnabled: true,
31
+ outputStyle: "explanatory",
32
+ env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
33
+ respectGitignore: true,
34
+ enableAllProjectMcpServers: true,
35
+ },
36
+ },
37
+ {
38
+ id: "developer-essentials",
39
+ name: "Developer Essentials",
40
+ description:
41
+ "Must Have + full dev toolkit: code analysis, browser, terminal, design, review",
42
+ icon: "▶",
43
+ magusPlugins: [
44
+ "statusline",
45
+ "multimodel",
46
+ "code-analysis",
47
+ "dev",
48
+ "browser-use",
49
+ "designer",
50
+ "terminal",
51
+ "kanban",
52
+ ],
53
+ anthropicPlugins: [
54
+ "claude-code-setup",
55
+ "claude-md-management",
56
+ "code-simplifier",
57
+ "explanatory-output-style",
58
+ "playground",
59
+ "skill-creator",
60
+ "code-review",
61
+ "commit-commands",
62
+ "feature-dev",
63
+ ],
64
+ skills: ["Find Skills", "Systematic Debugging"],
65
+ settings: {
66
+ effortLevel: "high",
67
+ alwaysThinkingEnabled: true,
68
+ outputStyle: "explanatory",
69
+ env: {
70
+ CLAUDE_CODE_ENABLE_TASKS: "true",
71
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
72
+ },
73
+ includeGitInstructions: true,
74
+ respectGitignore: true,
75
+ enableAllProjectMcpServers: true,
76
+ },
77
+ },
13
78
  {
14
79
  id: "frontend-pro",
15
80
  name: "Frontend Pro",
16
- description: "UI implementation, design fidelity, browser-driven workflows",
17
- icon: "🎨",
81
+ description:
82
+ "UI implementation, design fidelity, browser-driven workflows",
83
+ icon: "★",
18
84
  magusPlugins: [
19
85
  "dev",
20
86
  "code-analysis",
@@ -41,7 +107,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
41
107
  settings: {
42
108
  effortLevel: "high",
43
109
  alwaysThinkingEnabled: true,
44
- model: "claude-sonnet-4-6",
45
110
  outputStyle: "explanatory",
46
111
  env: {
47
112
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -56,7 +121,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
56
121
  id: "backend-forge",
57
122
  name: "Backend Forge",
58
123
  description: "API development, debugging, code quality, data workflows",
59
- icon: "⚙️",
124
+ icon: "",
60
125
  magusPlugins: [
61
126
  "dev",
62
127
  "code-analysis",
@@ -72,13 +137,11 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
72
137
  "code-simplifier",
73
138
  "commit-commands",
74
139
  "security-guidance",
75
- "agent-sdk-dev",
76
140
  ],
77
141
  skills: ["Systematic Debugging", "Neon Postgres", "Find Skills"],
78
142
  settings: {
79
143
  effortLevel: "high",
80
144
  alwaysThinkingEnabled: true,
81
- model: "claude-sonnet-4-6",
82
145
  outputStyle: "concise",
83
146
  env: {
84
147
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -94,7 +157,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
94
157
  name: "Infra Ops",
95
158
  description:
96
159
  "Infrastructure, operational debugging, automation, terminal-first",
97
- icon: "🔧",
160
+ icon: "",
98
161
  magusPlugins: [
99
162
  "dev",
100
163
  "code-analysis",
@@ -116,7 +179,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
116
179
  settings: {
117
180
  effortLevel: "high",
118
181
  alwaysThinkingEnabled: true,
119
- model: "claude-opus-4-6",
120
182
  outputStyle: "concise",
121
183
  env: {
122
184
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -132,7 +194,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
132
194
  name: "Growth Marketer",
133
195
  description:
134
196
  "SEO, website audits, content production, marketing automation",
135
- icon: "📈",
197
+ icon: "",
136
198
  magusPlugins: [
137
199
  "dev",
138
200
  "code-analysis",
@@ -156,8 +218,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
156
218
  ],
157
219
  settings: {
158
220
  effortLevel: "medium",
159
- alwaysThinkingEnabled: false,
160
- model: "claude-sonnet-4-6",
161
221
  outputStyle: "explanatory",
162
222
  env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
163
223
  respectGitignore: true,
@@ -169,7 +229,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
169
229
  name: "Team Lead",
170
230
  description:
171
231
  "Planning, code review, coordination, and broad repo visibility",
172
- icon: "👔",
232
+ icon: "",
173
233
  magusPlugins: [
174
234
  "dev",
175
235
  "code-analysis",
@@ -191,7 +251,6 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
191
251
  settings: {
192
252
  effortLevel: "medium",
193
253
  alwaysThinkingEnabled: true,
194
- model: "claude-sonnet-4-6",
195
254
  outputStyle: "explanatory",
196
255
  env: {
197
256
  CLAUDE_CODE_ENABLE_TASKS: "true",
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import { UpdateCache } from "../services/update-cache.js";
5
5
  import { getAvailablePlugins, clearMarketplaceCache, } from "../services/plugin-manager.js";
6
6
  import { runClaude } from "../services/claude-runner.js";
7
- import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, saveGlobalInstalledPluginVersion, } from "../services/claude-settings.js";
7
+ import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, saveGlobalInstalledPluginVersion, readGlobalSettings, writeGlobalSettings, } from "../services/claude-settings.js";
8
8
  import { parsePluginId } from "../utils/string-utils.js";
9
9
  import { defaultMarketplaces } from "../data/marketplaces.js";
10
10
  import { updatePlugin, addMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
@@ -22,20 +22,26 @@ async function getReferencedMarketplaces(projectPath) {
22
22
  for (const id of Object.keys(global))
23
23
  allPluginIds.add(id);
24
24
  }
25
- catch { /* skip if unreadable */ }
25
+ catch {
26
+ /* skip if unreadable */
27
+ }
26
28
  if (projectPath) {
27
29
  try {
28
30
  const project = await getEnabledPlugins(projectPath);
29
31
  for (const id of Object.keys(project))
30
32
  allPluginIds.add(id);
31
33
  }
32
- catch { /* skip if unreadable */ }
34
+ catch {
35
+ /* skip if unreadable */
36
+ }
33
37
  try {
34
38
  const local = await getLocalEnabledPlugins(projectPath);
35
39
  for (const id of Object.keys(local))
36
40
  allPluginIds.add(id);
37
41
  }
38
- catch { /* skip if unreadable */ }
42
+ catch {
43
+ /* skip if unreadable */
44
+ }
39
45
  }
40
46
  // Parse marketplace names from plugin IDs
41
47
  for (const pluginId of allPluginIds) {
@@ -73,6 +79,57 @@ async function autoAddMissingMarketplaces(projectPath) {
73
79
  }
74
80
  return added;
75
81
  }
82
+ const CONTINUITY_PLUGIN_SENTINEL = "tmux-claude-continuity";
83
+ const CONTINUITY_PLUGIN_SCRIPT = path.join(os.homedir(), ".tmux", "plugins", "tmux-claude-continuity", "scripts", "on_session_start.sh");
84
+ /**
85
+ * Ensure tmux-claude-continuity Claude Code hooks are present in global settings.
86
+ * If the tmux plugin is installed but the hooks are missing, they are appended.
87
+ * Returns a description of what was added, or null if nothing changed.
88
+ */
89
+ async function ensureTmuxContinuityHooks() {
90
+ // Plugin not installed — nothing to do
91
+ if (!(await fs.pathExists(CONTINUITY_PLUGIN_SCRIPT))) {
92
+ return null;
93
+ }
94
+ const settings = await readGlobalSettings();
95
+ // Check if hooks are already configured by looking for the sentinel string
96
+ const existingHooks = settings.hooks ?? {};
97
+ for (const groups of Object.values(existingHooks)) {
98
+ for (const group of groups) {
99
+ for (const hook of group.hooks) {
100
+ if (hook.command.includes(CONTINUITY_PLUGIN_SENTINEL)) {
101
+ return null; // Already configured
102
+ }
103
+ }
104
+ }
105
+ }
106
+ // Append hooks, preserving any existing entries in SessionStart and Stop
107
+ const sessionStartGroups = existingHooks["SessionStart"] ?? [];
108
+ const stopGroups = existingHooks["Stop"] ?? [];
109
+ sessionStartGroups.push({
110
+ hooks: [
111
+ {
112
+ type: "command",
113
+ command: "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_session_start.sh",
114
+ },
115
+ ],
116
+ });
117
+ stopGroups.push({
118
+ hooks: [
119
+ {
120
+ type: "command",
121
+ command: "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_stop.sh",
122
+ },
123
+ ],
124
+ });
125
+ settings.hooks = {
126
+ ...existingHooks,
127
+ SessionStart: sessionStartGroups,
128
+ Stop: stopGroups,
129
+ };
130
+ await writeGlobalSettings(settings);
131
+ return "SessionStart + Stop hooks";
132
+ }
76
133
  /**
77
134
  * Prerun orchestration: Check for updates, apply them, then run claude
78
135
  * @param claudeArgs - Arguments to pass to claude CLI
@@ -84,9 +141,11 @@ export async function prerunClaude(claudeArgs, options = {}) {
84
141
  try {
85
142
  // STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
86
143
  const migration = await migrateMarketplaceRename();
87
- const migTotal = migration.projectMigrated + migration.globalMigrated
88
- + migration.localMigrated + migration.registryMigrated
89
- + (migration.knownMarketplacesMigrated ? 1 : 0);
144
+ const migTotal = migration.projectMigrated +
145
+ migration.globalMigrated +
146
+ migration.localMigrated +
147
+ migration.registryMigrated +
148
+ (migration.knownMarketplacesMigrated ? 1 : 0);
90
149
  if (migTotal > 0) {
91
150
  console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
92
151
  }
@@ -99,6 +158,11 @@ export async function prerunClaude(claudeArgs, options = {}) {
99
158
  console.log(`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`);
100
159
  }
101
160
  }
161
+ // STEP 0.6: Ensure tmux-claude-continuity hooks are configured
162
+ const addedHooks = await ensureTmuxContinuityHooks();
163
+ if (addedHooks) {
164
+ console.log(`✓ Added tmux-claude-continuity hooks to ~/.claude/settings.json`);
165
+ }
102
166
  // STEP 1: Check if we should update (time-based cache, or forced)
103
167
  const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
104
168
  if (options.force) {
@@ -14,6 +14,8 @@ import {
14
14
  getEnabledPlugins,
15
15
  getLocalEnabledPlugins,
16
16
  saveGlobalInstalledPluginVersion,
17
+ readGlobalSettings,
18
+ writeGlobalSettings,
17
19
  } from "../services/claude-settings.js";
18
20
  import { parsePluginId } from "../utils/string-utils.js";
19
21
  import { defaultMarketplaces } from "../data/marketplaces.js";
@@ -49,18 +51,24 @@ async function getReferencedMarketplaces(
49
51
  try {
50
52
  const global = await getGlobalEnabledPlugins();
51
53
  for (const id of Object.keys(global)) allPluginIds.add(id);
52
- } catch { /* skip if unreadable */ }
54
+ } catch {
55
+ /* skip if unreadable */
56
+ }
53
57
 
54
58
  if (projectPath) {
55
59
  try {
56
60
  const project = await getEnabledPlugins(projectPath);
57
61
  for (const id of Object.keys(project)) allPluginIds.add(id);
58
- } catch { /* skip if unreadable */ }
62
+ } catch {
63
+ /* skip if unreadable */
64
+ }
59
65
 
60
66
  try {
61
67
  const local = await getLocalEnabledPlugins(projectPath);
62
68
  for (const id of Object.keys(local)) allPluginIds.add(id);
63
- } catch { /* skip if unreadable */ }
69
+ } catch {
70
+ /* skip if unreadable */
71
+ }
64
72
  }
65
73
 
66
74
  // Parse marketplace names from plugin IDs
@@ -108,6 +116,75 @@ async function autoAddMissingMarketplaces(
108
116
  return added;
109
117
  }
110
118
 
119
+ const CONTINUITY_PLUGIN_SENTINEL = "tmux-claude-continuity";
120
+ const CONTINUITY_PLUGIN_SCRIPT = path.join(
121
+ os.homedir(),
122
+ ".tmux",
123
+ "plugins",
124
+ "tmux-claude-continuity",
125
+ "scripts",
126
+ "on_session_start.sh",
127
+ );
128
+
129
+ /**
130
+ * Ensure tmux-claude-continuity Claude Code hooks are present in global settings.
131
+ * If the tmux plugin is installed but the hooks are missing, they are appended.
132
+ * Returns a description of what was added, or null if nothing changed.
133
+ */
134
+ async function ensureTmuxContinuityHooks(): Promise<string | null> {
135
+ // Plugin not installed — nothing to do
136
+ if (!(await fs.pathExists(CONTINUITY_PLUGIN_SCRIPT))) {
137
+ return null;
138
+ }
139
+
140
+ const settings = await readGlobalSettings();
141
+
142
+ // Check if hooks are already configured by looking for the sentinel string
143
+ const existingHooks = settings.hooks ?? {};
144
+ for (const groups of Object.values(existingHooks)) {
145
+ for (const group of groups) {
146
+ for (const hook of group.hooks) {
147
+ if (hook.command.includes(CONTINUITY_PLUGIN_SENTINEL)) {
148
+ return null; // Already configured
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Append hooks, preserving any existing entries in SessionStart and Stop
155
+ const sessionStartGroups = existingHooks["SessionStart"] ?? [];
156
+ const stopGroups = existingHooks["Stop"] ?? [];
157
+
158
+ sessionStartGroups.push({
159
+ hooks: [
160
+ {
161
+ type: "command",
162
+ command:
163
+ "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_session_start.sh",
164
+ },
165
+ ],
166
+ });
167
+
168
+ stopGroups.push({
169
+ hooks: [
170
+ {
171
+ type: "command",
172
+ command:
173
+ "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_stop.sh",
174
+ },
175
+ ],
176
+ });
177
+
178
+ settings.hooks = {
179
+ ...existingHooks,
180
+ SessionStart: sessionStartGroups,
181
+ Stop: stopGroups,
182
+ };
183
+
184
+ await writeGlobalSettings(settings);
185
+ return "SessionStart + Stop hooks";
186
+ }
187
+
111
188
  /**
112
189
  * Prerun orchestration: Check for updates, apply them, then run claude
113
190
  * @param claudeArgs - Arguments to pass to claude CLI
@@ -123,9 +200,12 @@ export async function prerunClaude(
123
200
  try {
124
201
  // STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
125
202
  const migration = await migrateMarketplaceRename();
126
- const migTotal = migration.projectMigrated + migration.globalMigrated
127
- + migration.localMigrated + migration.registryMigrated
128
- + (migration.knownMarketplacesMigrated ? 1 : 0);
203
+ const migTotal =
204
+ migration.projectMigrated +
205
+ migration.globalMigrated +
206
+ migration.localMigrated +
207
+ migration.registryMigrated +
208
+ (migration.knownMarketplacesMigrated ? 1 : 0);
129
209
  if (migTotal > 0) {
130
210
  console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
131
211
  }
@@ -142,6 +222,14 @@ export async function prerunClaude(
142
222
  }
143
223
  }
144
224
 
225
+ // STEP 0.6: Ensure tmux-claude-continuity hooks are configured
226
+ const addedHooks = await ensureTmuxContinuityHooks();
227
+ if (addedHooks) {
228
+ console.log(
229
+ `✓ Added tmux-claude-continuity hooks to ~/.claude/settings.json`,
230
+ );
231
+ }
232
+
145
233
  // STEP 1: Check if we should update (time-based cache, or forced)
146
234
  const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
147
235
 
@@ -12,7 +12,7 @@ import { execFile } from "node:child_process";
12
12
  import { promisify } from "node:util";
13
13
  import { which } from "../utils/command-utils.js";
14
14
  import { removeGlobalInstalledPluginVersion, removeLocalInstalledPluginVersion, } from "./claude-settings.js";
15
- import { removeInstalledPluginVersion, } from "./plugin-manager.js";
15
+ import { removeInstalledPluginVersion } from "./plugin-manager.js";
16
16
  const execFileAsync = promisify(execFile);
17
17
  /**
18
18
  * Get the path to the claude CLI binary
@@ -107,6 +107,13 @@ export async function updatePlugin(pluginId, scope = "user") {
107
107
  export async function addMarketplace(repo) {
108
108
  await execClaude(["plugin", "marketplace", "add", repo], 60000);
109
109
  }
110
+ /**
111
+ * Update marketplace cache by running git pull via Claude CLI
112
+ * Uses longer timeout since this involves git network operations
113
+ */
114
+ export async function updateMarketplace(name) {
115
+ await execClaude(["plugin", "marketplace", "update", name], 60000);
116
+ }
110
117
  /**
111
118
  * Check if the claude CLI is available
112
119
  * @returns true if claude CLI is found in PATH