ccmanager 1.4.4 → 2.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.
Files changed (44) hide show
  1. package/README.md +34 -1
  2. package/dist/cli.d.ts +4 -0
  3. package/dist/cli.js +30 -2
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +67 -0
  6. package/dist/components/App.d.ts +1 -0
  7. package/dist/components/App.js +107 -37
  8. package/dist/components/Menu.d.ts +6 -1
  9. package/dist/components/Menu.js +227 -50
  10. package/dist/components/Menu.recent-projects.test.d.ts +1 -0
  11. package/dist/components/Menu.recent-projects.test.js +159 -0
  12. package/dist/components/Menu.test.d.ts +1 -0
  13. package/dist/components/Menu.test.js +196 -0
  14. package/dist/components/ProjectList.d.ts +10 -0
  15. package/dist/components/ProjectList.js +231 -0
  16. package/dist/components/ProjectList.recent-projects.test.d.ts +1 -0
  17. package/dist/components/ProjectList.recent-projects.test.js +186 -0
  18. package/dist/components/ProjectList.test.d.ts +1 -0
  19. package/dist/components/ProjectList.test.js +501 -0
  20. package/dist/components/Session.js +4 -14
  21. package/dist/constants/env.d.ts +3 -0
  22. package/dist/constants/env.js +4 -0
  23. package/dist/constants/error.d.ts +6 -0
  24. package/dist/constants/error.js +7 -0
  25. package/dist/hooks/useSearchMode.d.ts +15 -0
  26. package/dist/hooks/useSearchMode.js +67 -0
  27. package/dist/services/configurationManager.d.ts +1 -0
  28. package/dist/services/configurationManager.js +14 -7
  29. package/dist/services/globalSessionOrchestrator.d.ts +16 -0
  30. package/dist/services/globalSessionOrchestrator.js +73 -0
  31. package/dist/services/globalSessionOrchestrator.test.d.ts +1 -0
  32. package/dist/services/globalSessionOrchestrator.test.js +180 -0
  33. package/dist/services/projectManager.d.ts +60 -0
  34. package/dist/services/projectManager.js +418 -0
  35. package/dist/services/projectManager.test.d.ts +1 -0
  36. package/dist/services/projectManager.test.js +342 -0
  37. package/dist/services/sessionManager.d.ts +8 -0
  38. package/dist/services/sessionManager.js +41 -7
  39. package/dist/services/sessionManager.test.js +79 -0
  40. package/dist/services/worktreeService.d.ts +1 -0
  41. package/dist/services/worktreeService.js +20 -5
  42. package/dist/services/worktreeService.test.js +72 -0
  43. package/dist/types/index.d.ts +55 -0
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CCManager - AI Code Assistant Session Manager
2
2
 
3
- CCManager is a TUI application for managing multiple AI coding assistant sessions (Claude Code, Gemini CLI) across Git worktrees.
3
+ CCManager is a TUI application for managing multiple AI coding assistant sessions (Claude Code, Gemini CLI) across Git worktrees and projects.
4
4
 
5
5
 
6
6
 
@@ -11,6 +11,7 @@ https://github.com/user-attachments/assets/15914a88-e288-4ac9-94d5-8127f2e19dbf
11
11
  ## Features
12
12
 
13
13
  - Run multiple AI assistant sessions in parallel across different Git worktrees
14
+ - **Multi-project support**: Manage multiple git repositories from a single interface
14
15
  - Support for multiple AI coding assistants (Claude Code, Gemini CLI)
15
16
  - Switch between sessions seamlessly
16
17
  - Visual status indicators for session states (busy, waiting, idle)
@@ -243,6 +244,38 @@ The devcontainer integration requires both commands:
243
244
 
244
245
  For detailed setup and configuration, see [docs/devcontainer.md](docs/devcontainer.md).
245
246
 
247
+ ## Multi-Project Mode
248
+
249
+ CCManager can manage multiple git repositories from a single interface, allowing you to organize and navigate between different projects and their worktrees efficiently.
250
+
251
+ ### Quick Start
252
+
253
+ ```bash
254
+ # Set the root directory containing your git projects
255
+ export CCMANAGER_MULTI_PROJECT_ROOT="/path/to/your/projects"
256
+
257
+ # Run CCManager in multi-project mode
258
+ npx ccmanager --multi-project
259
+ ```
260
+
261
+ ### Features
262
+
263
+ - **Automatic project discovery**: Recursively finds all git repositories
264
+ - **Recent projects**: Frequently used projects appear at the top
265
+ - **Vi-like search**: Press `/` to filter projects or worktrees
266
+ - **Session persistence**: Sessions remain active when switching projects
267
+ - **Visual indicators**: See session counts `[active/busy/waiting]` for each project
268
+
269
+ ### Navigation
270
+
271
+ 1. **Project List**: Select from all discovered git repositories
272
+ 2. **Worktree Menu**: Manage worktrees for the selected project
273
+ 3. **Session View**: Interact with your AI assistant
274
+
275
+ Use `B` key to navigate back from worktrees to project list.
276
+
277
+ For detailed configuration and usage, see [docs/multi-project.md](docs/multi-project.md).
278
+
246
279
  ## Git Worktree Configuration
247
280
 
248
281
  CCManager can display enhanced git status information for each worktree when Git's worktree configuration extension is enabled.
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  export declare const parsedArgs: import("meow").Result<{
3
+ multiProject: {
4
+ type: "boolean";
5
+ default: false;
6
+ };
3
7
  devcUpCommand: {
4
8
  type: "string";
5
9
  };
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { render } from 'ink';
4
4
  import meow from 'meow';
5
5
  import App from './components/App.js';
6
6
  import { worktreeConfigManager } from './services/worktreeConfigManager.js';
7
+ import { globalSessionOrchestrator } from './services/globalSessionOrchestrator.js';
7
8
  const cli = meow(`
8
9
  Usage
9
10
  $ ccmanager
@@ -11,15 +12,21 @@ const cli = meow(`
11
12
  Options
12
13
  --help Show help
13
14
  --version Show version
15
+ --multi-project Enable multi-project mode
14
16
  --devc-up-command Command to start devcontainer
15
17
  --devc-exec-command Command to execute in devcontainer
16
18
 
17
19
  Examples
18
20
  $ ccmanager
21
+ $ ccmanager --multi-project
19
22
  $ ccmanager --devc-up-command "devcontainer up --workspace-folder ." --devc-exec-command "devcontainer exec --workspace-folder ."
20
23
  `, {
21
24
  importMeta: import.meta,
22
25
  flags: {
26
+ multiProject: {
27
+ type: 'boolean',
28
+ default: false,
29
+ },
23
30
  devcUpCommand: {
24
31
  type: 'string',
25
32
  },
@@ -38,6 +45,13 @@ if (!process.stdin.isTTY || !process.stdout.isTTY) {
38
45
  console.error('Error: ccmanager must be run in an interactive terminal (TTY)');
39
46
  process.exit(1);
40
47
  }
48
+ // Check for CCMANAGER_MULTI_PROJECT_ROOT when using --multi-project
49
+ if (cli.flags.multiProject && !process.env['CCMANAGER_MULTI_PROJECT_ROOT']) {
50
+ console.error('Error: CCMANAGER_MULTI_PROJECT_ROOT environment variable must be set when using --multi-project');
51
+ console.error('Please set it to the root directory containing your projects, e.g.:');
52
+ console.error(' export CCMANAGER_MULTI_PROJECT_ROOT=/path/to/projects');
53
+ process.exit(1);
54
+ }
41
55
  // Initialize worktree config manager
42
56
  worktreeConfigManager.initialize();
43
57
  // Prepare devcontainer config
@@ -48,7 +62,21 @@ const devcontainerConfig = cli.flags.devcUpCommand && cli.flags.devcExecCommand
48
62
  }
49
63
  : undefined;
50
64
  // Pass config to App
51
- const appProps = devcontainerConfig ? { devcontainerConfig } : {};
52
- render(React.createElement(App, { ...appProps }));
65
+ const appProps = {
66
+ ...(devcontainerConfig ? { devcontainerConfig } : {}),
67
+ multiProject: cli.flags.multiProject,
68
+ };
69
+ const app = render(React.createElement(App, { ...appProps }));
70
+ // Clean up sessions on exit
71
+ process.on('SIGINT', () => {
72
+ globalSessionOrchestrator.destroyAllSessions();
73
+ app.unmount();
74
+ process.exit(0);
75
+ });
76
+ process.on('SIGTERM', () => {
77
+ globalSessionOrchestrator.destroyAllSessions();
78
+ app.unmount();
79
+ process.exit(0);
80
+ });
53
81
  // Export for testing
54
82
  export const parsedArgs = cli;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { spawn } from 'child_process';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ describe('CLI', () => {
9
+ let originalEnv;
10
+ beforeEach(() => {
11
+ originalEnv = { ...process.env };
12
+ });
13
+ afterEach(() => {
14
+ process.env = originalEnv;
15
+ });
16
+ describe('--multi-project flag', () => {
17
+ it('should exit with error when CCMANAGER_MULTI_PROJECT_ROOT is not set', async () => {
18
+ // Ensure the env var is not set
19
+ delete process.env['CCMANAGER_MULTI_PROJECT_ROOT'];
20
+ // Create a wrapper script that mocks TTY
21
+ const wrapperScript = `
22
+ process.stdin.isTTY = true;
23
+ process.stdout.isTTY = true;
24
+ process.stderr.isTTY = true;
25
+ process.argv = ['node', 'cli.js', '--multi-project'];
26
+ import('./cli.js');
27
+ `;
28
+ const result = await new Promise(resolve => {
29
+ const proc = spawn('node', ['--input-type=module', '-e', wrapperScript], {
30
+ cwd: path.join(__dirname, '../dist'),
31
+ env: { ...process.env },
32
+ stdio: ['pipe', 'pipe', 'pipe'],
33
+ });
34
+ let stderr = '';
35
+ proc.stderr?.on('data', data => {
36
+ stderr += data.toString();
37
+ });
38
+ proc.on('close', code => {
39
+ resolve({ code: code ?? 1, stderr });
40
+ });
41
+ });
42
+ expect(result.code).toBe(1);
43
+ expect(result.stderr).toContain('CCMANAGER_MULTI_PROJECT_ROOT environment variable must be set');
44
+ expect(result.stderr).toContain('export CCMANAGER_MULTI_PROJECT_ROOT=/path/to/projects');
45
+ });
46
+ it('should not check for env var when --multi-project is not used', async () => {
47
+ // Ensure the env var is not set
48
+ delete process.env['CCMANAGER_MULTI_PROJECT_ROOT'];
49
+ const result = await new Promise(resolve => {
50
+ const cliPath = path.join(__dirname, '../dist/cli.js');
51
+ const proc = spawn('node', [cliPath, '--help'], {
52
+ env: { ...process.env },
53
+ stdio: ['pipe', 'pipe', 'pipe'],
54
+ });
55
+ let stderr = '';
56
+ proc.stderr?.on('data', data => {
57
+ stderr += data.toString();
58
+ });
59
+ proc.on('close', code => {
60
+ resolve({ code: code ?? 1, stderr });
61
+ });
62
+ });
63
+ expect(result.code).toBe(0);
64
+ expect(result.stderr).not.toContain('CCMANAGER_MULTI_PROJECT_ROOT');
65
+ });
66
+ });
67
+ });
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { DevcontainerConfig } from '../types/index.js';
3
3
  interface AppProps {
4
4
  devcontainerConfig?: DevcontainerConfig;
5
+ multiProject?: boolean;
5
6
  }
6
7
  declare const App: React.FC<AppProps>;
7
8
  export default App;
@@ -1,25 +1,46 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
2
  import { useApp, Box, Text } from 'ink';
3
3
  import Menu from './Menu.js';
4
+ import ProjectList from './ProjectList.js';
4
5
  import Session from './Session.js';
5
6
  import NewWorktree from './NewWorktree.js';
6
7
  import DeleteWorktree from './DeleteWorktree.js';
7
8
  import MergeWorktree from './MergeWorktree.js';
8
9
  import Configuration from './Configuration.js';
9
10
  import PresetSelector from './PresetSelector.js';
10
- import { SessionManager } from '../services/sessionManager.js';
11
+ import { globalSessionOrchestrator } from '../services/globalSessionOrchestrator.js';
11
12
  import { WorktreeService } from '../services/worktreeService.js';
12
13
  import { shortcutManager } from '../services/shortcutManager.js';
13
14
  import { configurationManager } from '../services/configurationManager.js';
14
- const App = ({ devcontainerConfig }) => {
15
+ import { ENV_VARS } from '../constants/env.js';
16
+ import { MULTI_PROJECT_ERRORS } from '../constants/error.js';
17
+ import { projectManager } from '../services/projectManager.js';
18
+ const App = ({ devcontainerConfig, multiProject }) => {
15
19
  const { exit } = useApp();
16
- const [view, setView] = useState('menu');
17
- const [sessionManager] = useState(() => new SessionManager());
18
- const [worktreeService] = useState(() => new WorktreeService());
20
+ const [view, setView] = useState(multiProject ? 'project-list' : 'menu');
21
+ const [sessionManager, setSessionManager] = useState(() => globalSessionOrchestrator.getManagerForProject());
22
+ const [worktreeService, setWorktreeService] = useState(() => new WorktreeService());
19
23
  const [activeSession, setActiveSession] = useState(null);
20
24
  const [error, setError] = useState(null);
21
25
  const [menuKey, setMenuKey] = useState(0); // Force menu refresh
22
26
  const [selectedWorktree, setSelectedWorktree] = useState(null); // Store selected worktree for preset selection
27
+ const [selectedProject, setSelectedProject] = useState(null); // Store selected project in multi-project mode
28
+ // Helper function to clear terminal screen
29
+ const clearScreen = () => {
30
+ if (process.stdout.isTTY) {
31
+ process.stdout.write('\x1B[2J\x1B[H');
32
+ }
33
+ };
34
+ // Helper function to navigate with screen clearing
35
+ const navigateWithClear = useCallback((newView, callback) => {
36
+ clearScreen();
37
+ setView('clearing');
38
+ setTimeout(() => {
39
+ setView(newView);
40
+ if (callback)
41
+ callback();
42
+ }, 10); // Small delay to ensure screen clear is processed
43
+ }, []);
23
44
  useEffect(() => {
24
45
  // Listen for session exits to return to menu automatically
25
46
  const handleSessionExit = (session) => {
@@ -27,53 +48,61 @@ const App = ({ devcontainerConfig }) => {
27
48
  setActiveSession(current => {
28
49
  if (current && session.id === current.id) {
29
50
  // Session that exited is the active one, trigger return to menu
30
- setTimeout(() => {
31
- setActiveSession(null);
32
- setError(null);
33
- setView('menu');
51
+ setActiveSession(null);
52
+ setError(null);
53
+ const targetView = multiProject && selectedProject
54
+ ? 'menu'
55
+ : multiProject
56
+ ? 'project-list'
57
+ : 'menu';
58
+ navigateWithClear(targetView, () => {
34
59
  setMenuKey(prev => prev + 1);
35
- if (process.stdout.isTTY) {
36
- process.stdout.write('\x1B[2J\x1B[H');
37
- }
38
60
  process.stdin.resume();
39
61
  process.stdin.setEncoding('utf8');
40
- }, 0);
62
+ });
41
63
  }
42
64
  return current;
43
65
  });
44
66
  };
45
67
  sessionManager.on('sessionExit', handleSessionExit);
46
- // Cleanup on unmount
68
+ // Re-attach listener when session manager changes
47
69
  return () => {
48
70
  sessionManager.off('sessionExit', handleSessionExit);
49
- sessionManager.destroy();
71
+ // Don't destroy sessions on unmount - they persist in memory
50
72
  };
51
- }, [sessionManager]);
73
+ }, [sessionManager, multiProject, selectedProject, navigateWithClear]);
52
74
  const handleSelectWorktree = async (worktree) => {
53
75
  // Check if this is the new worktree option
54
76
  if (worktree.path === '') {
55
- setView('new-worktree');
77
+ navigateWithClear('new-worktree');
56
78
  return;
57
79
  }
58
80
  // Check if this is the delete worktree option
59
81
  if (worktree.path === 'DELETE_WORKTREE') {
60
- setView('delete-worktree');
82
+ navigateWithClear('delete-worktree');
61
83
  return;
62
84
  }
63
85
  // Check if this is the merge worktree option
64
86
  if (worktree.path === 'MERGE_WORKTREE') {
65
- setView('merge-worktree');
87
+ navigateWithClear('merge-worktree');
66
88
  return;
67
89
  }
68
90
  // Check if this is the configuration option
69
91
  if (worktree.path === 'CONFIGURATION') {
70
- setView('configuration');
92
+ navigateWithClear('configuration');
71
93
  return;
72
94
  }
73
95
  // Check if this is the exit application option
74
96
  if (worktree.path === 'EXIT_APPLICATION') {
75
- sessionManager.destroy();
76
- exit();
97
+ // In multi-project mode with a selected project, go back to project list
98
+ if (multiProject && selectedProject) {
99
+ handleBackToProjectList();
100
+ }
101
+ else {
102
+ // Only destroy all sessions when actually exiting the app
103
+ globalSessionOrchestrator.destroyAllSessions();
104
+ exit();
105
+ }
77
106
  return;
78
107
  }
79
108
  // Get or create session for this worktree
@@ -82,7 +111,7 @@ const App = ({ devcontainerConfig }) => {
82
111
  // Check if we should show preset selector
83
112
  if (configurationManager.getSelectPresetOnStart()) {
84
113
  setSelectedWorktree(worktree);
85
- setView('preset-selector');
114
+ navigateWithClear('preset-selector');
86
115
  return;
87
116
  }
88
117
  try {
@@ -100,7 +129,7 @@ const App = ({ devcontainerConfig }) => {
100
129
  }
101
130
  }
102
131
  setActiveSession(session);
103
- setView('session');
132
+ navigateWithClear('session');
104
133
  };
105
134
  const handlePresetSelected = async (presetId) => {
106
135
  if (!selectedWorktree)
@@ -115,7 +144,7 @@ const App = ({ devcontainerConfig }) => {
115
144
  session = await sessionManager.createSessionWithPreset(selectedWorktree.path, presetId);
116
145
  }
117
146
  setActiveSession(session);
118
- setView('session');
147
+ navigateWithClear('session');
119
148
  setSelectedWorktree(null);
120
149
  }
121
150
  catch (error) {
@@ -126,20 +155,20 @@ const App = ({ devcontainerConfig }) => {
126
155
  };
127
156
  const handlePresetSelectorCancel = () => {
128
157
  setSelectedWorktree(null);
129
- setView('menu');
130
- setMenuKey(prev => prev + 1);
158
+ navigateWithClear('menu', () => {
159
+ setMenuKey(prev => prev + 1);
160
+ });
131
161
  };
132
162
  const handleReturnToMenu = () => {
133
163
  setActiveSession(null);
134
164
  // Don't clear error here - let user dismiss it manually
135
- // Add a small delay to ensure Session cleanup completes
136
- setTimeout(() => {
137
- setView('menu');
165
+ const targetView = multiProject && selectedProject
166
+ ? 'menu'
167
+ : multiProject
168
+ ? 'project-list'
169
+ : 'menu';
170
+ navigateWithClear(targetView, () => {
138
171
  setMenuKey(prev => prev + 1); // Force menu refresh
139
- // Clear the screen when returning to menu
140
- if (process.stdout.isTTY) {
141
- process.stdout.write('\x1B[2J\x1B[H');
142
- }
143
172
  // Ensure stdin is in a clean state for Ink components
144
173
  if (process.stdin.isTTY) {
145
174
  // Flush any pending input to prevent escape sequences from leaking
@@ -148,7 +177,7 @@ const App = ({ devcontainerConfig }) => {
148
177
  process.stdin.resume();
149
178
  process.stdin.setEncoding('utf8');
150
179
  }
151
- }, 50); // Small delay to ensure proper cleanup
180
+ });
152
181
  };
153
182
  const handleCreateWorktree = async (path, branch, baseBranch, copySessionData, copyClaudeDirectory) => {
154
183
  setView('creating-worktree');
@@ -193,8 +222,45 @@ const App = ({ devcontainerConfig }) => {
193
222
  const handleCancelDeleteWorktree = () => {
194
223
  handleReturnToMenu();
195
224
  };
225
+ const handleSelectProject = (project) => {
226
+ // Handle special exit case
227
+ if (project.path === 'EXIT_APPLICATION') {
228
+ globalSessionOrchestrator.destroyAllSessions();
229
+ exit();
230
+ return;
231
+ }
232
+ // Set the selected project and update services
233
+ setSelectedProject(project);
234
+ setWorktreeService(new WorktreeService(project.path));
235
+ // Get or create session manager for this project
236
+ const projectSessionManager = globalSessionOrchestrator.getManagerForProject(project.path);
237
+ setSessionManager(projectSessionManager);
238
+ // Add to recent projects
239
+ projectManager.addRecentProject(project);
240
+ navigateWithClear('menu');
241
+ };
242
+ const handleBackToProjectList = () => {
243
+ // Sessions persist in their project-specific managers
244
+ setSelectedProject(null);
245
+ setWorktreeService(new WorktreeService()); // Reset to default
246
+ // Reset to global session manager for project list view
247
+ setSessionManager(globalSessionOrchestrator.getManagerForProject());
248
+ navigateWithClear('project-list', () => {
249
+ setMenuKey(prev => prev + 1);
250
+ });
251
+ };
252
+ if (view === 'project-list' && multiProject) {
253
+ const projectsDir = process.env[ENV_VARS.MULTI_PROJECT_ROOT];
254
+ if (!projectsDir) {
255
+ return (React.createElement(Box, null,
256
+ React.createElement(Text, { color: "red" },
257
+ "Error: ",
258
+ MULTI_PROJECT_ERRORS.NO_PROJECTS_DIR)));
259
+ }
260
+ return (React.createElement(ProjectList, { projectsDir: projectsDir, onSelectProject: handleSelectProject, error: error, onDismissError: () => setError(null) }));
261
+ }
196
262
  if (view === 'menu') {
197
- return (React.createElement(Menu, { key: menuKey, sessionManager: sessionManager, onSelectWorktree: handleSelectWorktree, error: error, onDismissError: () => setError(null) }));
263
+ return (React.createElement(Menu, { key: menuKey, sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: handleSelectWorktree, onSelectRecentProject: handleSelectProject, error: error, onDismissError: () => setError(null), projectName: selectedProject?.name, multiProject: multiProject }));
198
264
  }
199
265
  if (view === 'session' && activeSession) {
200
266
  return (React.createElement(Box, { flexDirection: "column" },
@@ -243,6 +309,10 @@ const App = ({ devcontainerConfig }) => {
243
309
  if (view === 'preset-selector') {
244
310
  return (React.createElement(PresetSelector, { onSelect: handlePresetSelected, onCancel: handlePresetSelectorCancel }));
245
311
  }
312
+ if (view === 'clearing') {
313
+ // Render nothing during the clearing phase to ensure clean transition
314
+ return null;
315
+ }
246
316
  return null;
247
317
  };
248
318
  export default App;
@@ -1,11 +1,16 @@
1
1
  import React from 'react';
2
- import { Worktree } from '../types/index.js';
2
+ import { Worktree, GitProject } from '../types/index.js';
3
+ import { WorktreeService } from '../services/worktreeService.js';
3
4
  import { SessionManager } from '../services/sessionManager.js';
4
5
  interface MenuProps {
5
6
  sessionManager: SessionManager;
7
+ worktreeService: WorktreeService;
6
8
  onSelectWorktree: (worktree: Worktree) => void;
9
+ onSelectRecentProject?: (project: GitProject) => void;
7
10
  error?: string | null;
8
11
  onDismissError?: () => void;
12
+ projectName?: string;
13
+ multiProject?: boolean;
9
14
  }
10
15
  declare const Menu: React.FC<MenuProps>;
11
16
  export default Menu;