ccmanager 3.9.0 → 3.11.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 (31) hide show
  1. package/dist/components/App.js +159 -44
  2. package/dist/components/App.test.js +96 -5
  3. package/dist/components/Dashboard.d.ts +12 -0
  4. package/dist/components/Dashboard.js +443 -0
  5. package/dist/components/Dashboard.test.js +348 -0
  6. package/dist/components/Menu.recent-projects.test.js +19 -19
  7. package/dist/components/NewWorktree.d.ts +20 -1
  8. package/dist/components/NewWorktree.js +103 -56
  9. package/dist/components/NewWorktree.test.js +17 -4
  10. package/dist/services/globalSessionOrchestrator.d.ts +1 -0
  11. package/dist/services/globalSessionOrchestrator.js +3 -0
  12. package/dist/services/projectManager.d.ts +7 -1
  13. package/dist/services/projectManager.js +26 -10
  14. package/dist/services/sessionManager.d.ts +3 -2
  15. package/dist/services/sessionManager.js +37 -40
  16. package/dist/services/sessionManager.test.js +38 -0
  17. package/dist/services/worktreeNameGenerator.d.ts +8 -0
  18. package/dist/services/worktreeNameGenerator.js +184 -0
  19. package/dist/services/worktreeNameGenerator.test.js +35 -0
  20. package/dist/utils/presetPrompt.d.ts +11 -0
  21. package/dist/utils/presetPrompt.js +71 -0
  22. package/dist/utils/presetPrompt.test.d.ts +1 -0
  23. package/dist/utils/presetPrompt.test.js +167 -0
  24. package/dist/utils/worktreeUtils.d.ts +1 -2
  25. package/package.json +6 -6
  26. package/dist/components/ProjectList.d.ts +0 -10
  27. package/dist/components/ProjectList.js +0 -233
  28. package/dist/components/ProjectList.recent-projects.test.js +0 -193
  29. package/dist/components/ProjectList.test.js +0 -620
  30. /package/dist/components/{ProjectList.recent-projects.test.d.ts → Dashboard.test.d.ts} +0 -0
  31. /package/dist/{components/ProjectList.test.d.ts → services/worktreeNameGenerator.test.d.ts} +0 -0
@@ -138,6 +138,44 @@ describe('SessionManager', () => {
138
138
  env: process.env,
139
139
  });
140
140
  });
141
+ it('passes the initial prompt as the final argument for claude-compatible presets', async () => {
142
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
143
+ id: '1',
144
+ name: 'Main',
145
+ command: 'claude',
146
+ args: ['--resume'],
147
+ detectionStrategy: 'claude',
148
+ });
149
+ vi.mocked(spawn).mockReturnValue(mockPty);
150
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree', undefined, 'implement prompt flow'));
151
+ expect(spawn).toHaveBeenCalledWith('claude', ['--resume', '--teammate-mode', 'in-process', 'implement prompt flow'], expect.any(Object));
152
+ expect(mockPty.write).not.toHaveBeenCalled();
153
+ });
154
+ it('passes the initial prompt with --prompt for opencode presets', async () => {
155
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
156
+ id: '1',
157
+ name: 'OpenCode',
158
+ command: 'opencode',
159
+ args: ['run'],
160
+ detectionStrategy: 'opencode',
161
+ });
162
+ vi.mocked(spawn).mockReturnValue(mockPty);
163
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree', undefined, 'implement prompt flow'));
164
+ expect(spawn).toHaveBeenCalledWith('opencode', ['run', '--prompt', 'implement prompt flow'], expect.any(Object));
165
+ expect(mockPty.write).not.toHaveBeenCalled();
166
+ });
167
+ it('writes the initial prompt to stdin for unknown commands', async () => {
168
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
169
+ id: '1',
170
+ name: 'Custom',
171
+ command: 'custom-agent',
172
+ args: ['--interactive'],
173
+ });
174
+ vi.mocked(spawn).mockReturnValue(mockPty);
175
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree', undefined, 'implement prompt flow'));
176
+ expect(spawn).toHaveBeenCalledWith('custom-agent', ['--interactive'], expect.any(Object));
177
+ expect(mockPty.write).toHaveBeenCalledWith('implement prompt flow\r');
178
+ });
141
179
  it('should fall back to default preset if specified preset not found', async () => {
142
180
  // Setup mocks
143
181
  vi.mocked(configReader.getPresetByIdEffect).mockReturnValue(Either.left(new ValidationError({
@@ -0,0 +1,8 @@
1
+ import { Effect } from 'effect';
2
+ import { ProcessError } from '../types/errors.js';
3
+ export declare const extractBranchNameFromOutput: (stdout: string) => string;
4
+ export declare const deduplicateBranchName: (name: string, existingBranches: string[]) => string;
5
+ export declare class WorktreeNameGenerator {
6
+ generateBranchNameEffect(userPrompt: string, baseBranch: string, existingBranches?: string[]): Effect.Effect<string, ProcessError, never>;
7
+ }
8
+ export declare const worktreeNameGenerator: WorktreeNameGenerator;
@@ -0,0 +1,184 @@
1
+ import { Effect } from 'effect';
2
+ import { execFile } from 'child_process';
3
+ import { ProcessError } from '../types/errors.js';
4
+ import { logger } from '../utils/logger.js';
5
+ const JSON_SCHEMA = JSON.stringify({
6
+ type: 'object',
7
+ properties: {
8
+ branchName: { type: 'string' },
9
+ },
10
+ required: ['branchName'],
11
+ additionalProperties: false,
12
+ });
13
+ const DEFAULT_TIMEOUT_MS = 30_000;
14
+ const buildPrompt = (userPrompt, baseBranch) => `You generate concise git branch names.
15
+
16
+ Base branch: ${baseBranch}
17
+ Task prompt:
18
+ ${userPrompt}
19
+
20
+ Return a short git branch name using lowercase letters, numbers, hyphens, and forward slashes only.
21
+ Do not include markdown, explanations, refs/heads/, or surrounding quotes.
22
+ Examples: feature/add-prompt-mode, fix/worktree-loading-state`;
23
+ const normalizeBranchName = (branchName) => {
24
+ const normalized = branchName
25
+ .trim()
26
+ .replace(/^```[a-z]*\n?/i, '')
27
+ .replace(/\n?```$/i, '')
28
+ .replace(/^refs\/heads\//, '')
29
+ .replace(/^"+|"+$/g, '')
30
+ .replace(/^'+|'+$/g, '')
31
+ .replace(/\s+/g, '-')
32
+ .replace(/[^a-zA-Z0-9/_-]/g, '-')
33
+ .replace(/-+/g, '-')
34
+ .replace(/\/+/g, '/')
35
+ .replace(/^-+|-+$/g, '')
36
+ .toLowerCase();
37
+ if (!normalized) {
38
+ throw new Error('Generated branch name was empty');
39
+ }
40
+ return normalized;
41
+ };
42
+ const extractStringCandidate = (value) => {
43
+ if (typeof value === 'string') {
44
+ return value;
45
+ }
46
+ if (Array.isArray(value)) {
47
+ for (const item of value) {
48
+ const nested = extractStringCandidate(item);
49
+ if (nested)
50
+ return nested;
51
+ }
52
+ }
53
+ if (typeof value === 'object' && value !== null) {
54
+ const obj = value;
55
+ for (const key of [
56
+ 'branchName',
57
+ 'result',
58
+ 'text',
59
+ 'content',
60
+ 'message',
61
+ 'completion',
62
+ ]) {
63
+ const nested = extractStringCandidate(obj[key]);
64
+ if (nested)
65
+ return nested;
66
+ }
67
+ }
68
+ return null;
69
+ };
70
+ export const extractBranchNameFromOutput = (stdout) => {
71
+ const trimmed = stdout.trim();
72
+ if (!trimmed) {
73
+ throw new Error('`claude -p` returned empty output');
74
+ }
75
+ for (const pattern of [
76
+ /"branchName"\s*:\s*"([^"]+)"/,
77
+ /branchName\s*[:=]\s*["']?([A-Za-z0-9/_-]+)["']?/,
78
+ ]) {
79
+ const match = trimmed.match(pattern);
80
+ if (match?.[1]) {
81
+ return normalizeBranchName(match[1]);
82
+ }
83
+ }
84
+ const jsonLines = trimmed
85
+ .split('\n')
86
+ .map(line => line.trim())
87
+ .filter(Boolean);
88
+ for (const line of [trimmed, ...jsonLines]) {
89
+ try {
90
+ const parsed = JSON.parse(line);
91
+ const candidate = extractStringCandidate(parsed);
92
+ if (candidate) {
93
+ return normalizeBranchName(candidate);
94
+ }
95
+ }
96
+ catch {
97
+ // Fall through to other parsing strategies.
98
+ }
99
+ }
100
+ const firstUsefulLine = trimmed
101
+ .split('\n')
102
+ .map(line => line.trim())
103
+ .find(line => line &&
104
+ !line.startsWith('{') &&
105
+ !line.startsWith('[') &&
106
+ !line.startsWith('```'));
107
+ if (firstUsefulLine) {
108
+ return normalizeBranchName(firstUsefulLine);
109
+ }
110
+ return normalizeBranchName(trimmed);
111
+ };
112
+ export const deduplicateBranchName = (name, existingBranches) => {
113
+ const lowerSet = new Set(existingBranches.map(b => b.toLowerCase()));
114
+ if (!lowerSet.has(name.toLowerCase())) {
115
+ return name;
116
+ }
117
+ for (let i = 2;; i++) {
118
+ const candidate = `${name}-${i}`;
119
+ if (!lowerSet.has(candidate.toLowerCase())) {
120
+ return candidate;
121
+ }
122
+ }
123
+ };
124
+ export class WorktreeNameGenerator {
125
+ generateBranchNameEffect(userPrompt, baseBranch, existingBranches) {
126
+ return Effect.tryPromise({
127
+ try: () => new Promise((resolve, reject) => {
128
+ let settled = false;
129
+ let child;
130
+ const settle = (handler) => {
131
+ if (settled)
132
+ return;
133
+ settled = true;
134
+ clearTimeout(timeoutId);
135
+ handler();
136
+ };
137
+ const timeoutId = setTimeout(() => {
138
+ settle(() => {
139
+ logger.warn('Worktree branch-name generation timed out, terminating claude helper');
140
+ child?.kill('SIGKILL');
141
+ reject(new Error('Timed out while generating a branch name with `claude -p`'));
142
+ });
143
+ }, DEFAULT_TIMEOUT_MS);
144
+ child = execFile('claude', ['-p', '--output-format', 'json', '--json-schema', JSON_SCHEMA], {
145
+ encoding: 'utf8',
146
+ maxBuffer: 1024 * 1024,
147
+ }, (error, stdout) => {
148
+ settle(() => {
149
+ if (error) {
150
+ reject(error);
151
+ return;
152
+ }
153
+ const branchName = extractBranchNameFromOutput(stdout);
154
+ resolve(existingBranches
155
+ ? deduplicateBranchName(branchName, existingBranches)
156
+ : branchName);
157
+ });
158
+ });
159
+ child.on('error', error => {
160
+ settle(() => reject(error));
161
+ });
162
+ if (!child.stdin) {
163
+ settle(() => reject(new Error('claude stdin unavailable')));
164
+ return;
165
+ }
166
+ child.stdin.write(buildPrompt(userPrompt, baseBranch));
167
+ child.stdin.end();
168
+ }),
169
+ catch: (error) => new ProcessError({
170
+ command: 'claude -p',
171
+ message: (typeof error === 'object' &&
172
+ error !== null &&
173
+ 'code' in error &&
174
+ error.code === 'ENOENT') ||
175
+ (error instanceof Error && error.message.includes('spawn claude'))
176
+ ? 'The `claude` command is required for automatic branch naming. Install it and make sure it is available in PATH.'
177
+ : error instanceof Error
178
+ ? `Failed to generate a branch name with \`claude -p\`: ${error.message}`
179
+ : 'Failed to generate branch name with `claude -p`',
180
+ }),
181
+ });
182
+ }
183
+ }
184
+ export const worktreeNameGenerator = new WorktreeNameGenerator();
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { deduplicateBranchName, extractBranchNameFromOutput, worktreeNameGenerator as generator, } from './worktreeNameGenerator.js';
3
+ describe('WorktreeNameGenerator output parsing', () => {
4
+ it('normalizes direct json branchName responses', async () => {
5
+ const value = extractBranchNameFromOutput('{"branchName":"feature/add prompt"}');
6
+ expect(value).toBe('feature/add-prompt');
7
+ expect(generator).toBeDefined();
8
+ });
9
+ it('extracts nested text payloads', async () => {
10
+ const value = extractBranchNameFromOutput('{"type":"result","result":{"text":"fix/worktree-loading-state"}}');
11
+ expect(value).toBe('fix/worktree-loading-state');
12
+ });
13
+ it('falls back to plain text output', async () => {
14
+ const value = extractBranchNameFromOutput('```text\nfeature/generated-name\n```');
15
+ expect(value).toBe('feature/generated-name');
16
+ });
17
+ it('extracts branchName from verbose result payloads', () => {
18
+ const value = extractBranchNameFromOutput('type":"result","subtype":"success","structured_output":{"branchName":"fix/trim-worktree-name"},"uuid":"123"');
19
+ expect(value).toBe('fix/trim-worktree-name');
20
+ });
21
+ });
22
+ describe('deduplicateBranchName', () => {
23
+ it('returns the name unchanged when no conflict exists', () => {
24
+ expect(deduplicateBranchName('feature/new', ['main', 'develop'])).toBe('feature/new');
25
+ });
26
+ it('appends -2 suffix when the name already exists', () => {
27
+ expect(deduplicateBranchName('feature/new', ['feature/new', 'main'])).toBe('feature/new-2');
28
+ });
29
+ it('increments the suffix when multiple conflicts exist', () => {
30
+ expect(deduplicateBranchName('fix/bug', ['fix/bug', 'fix/bug-2', 'fix/bug-3'])).toBe('fix/bug-4');
31
+ });
32
+ it('compares branch names case-insensitively', () => {
33
+ expect(deduplicateBranchName('Feature/New', ['feature/new'])).toBe('Feature/New-2');
34
+ });
35
+ });
@@ -0,0 +1,11 @@
1
+ import type { CommandPreset } from '../types/index.js';
2
+ export type PromptInjectionMethod = 'final-arg' | 'flag' | 'stdin';
3
+ export interface PreparedPresetLaunch {
4
+ args: string[];
5
+ stdinPayload?: string;
6
+ method: PromptInjectionMethod;
7
+ }
8
+ export declare const getPromptInjectionMethod: (preset: Pick<CommandPreset, "command" | "detectionStrategy">) => PromptInjectionMethod;
9
+ export declare const getPromptFlag: (preset: Pick<CommandPreset, "command" | "detectionStrategy">) => string | undefined;
10
+ export declare const describePromptInjection: (preset: Pick<CommandPreset, "command" | "detectionStrategy">) => string;
11
+ export declare const preparePresetLaunch: (preset: Pick<CommandPreset, "command" | "args" | "detectionStrategy">, prompt?: string) => PreparedPresetLaunch;
@@ -0,0 +1,71 @@
1
+ import { injectTeammateMode } from './commandArgs.js';
2
+ /**
3
+ * Prompt flag configuration per detectionStrategy.
4
+ * Sources:
5
+ * - opencode: `--prompt` https://opencode.ai/docs/cli/
6
+ * - gemini: `-i` (--prompt-interactive) https://google-gemini.github.io/gemini-cli/docs/cli/commands.html
7
+ * - github-copilot: `-i` https://docs.github.com/en/copilot/reference/cli-command-reference
8
+ * - kimi: `-p` https://www.kimi-cli.com/en/reference/kimi-command.html
9
+ */
10
+ const PROMPT_FLAG = {
11
+ opencode: '--prompt',
12
+ gemini: '-i',
13
+ 'github-copilot': '-i',
14
+ kimi: '-p',
15
+ };
16
+ export const getPromptInjectionMethod = (preset) => {
17
+ if (preset.detectionStrategy && PROMPT_FLAG[preset.detectionStrategy]) {
18
+ return 'flag';
19
+ }
20
+ if (preset.detectionStrategy === 'claude' ||
21
+ preset.detectionStrategy === 'codex' ||
22
+ preset.detectionStrategy === 'cursor' ||
23
+ preset.detectionStrategy === 'cline') {
24
+ return 'final-arg';
25
+ }
26
+ return 'stdin';
27
+ };
28
+ export const getPromptFlag = (preset) => {
29
+ if (preset.detectionStrategy) {
30
+ return PROMPT_FLAG[preset.detectionStrategy];
31
+ }
32
+ return undefined;
33
+ };
34
+ export const describePromptInjection = (preset) => {
35
+ const method = getPromptInjectionMethod(preset);
36
+ if (method === 'flag') {
37
+ const flag = getPromptFlag(preset) || '--prompt';
38
+ return `The prompt will be passed as a command flag: \`${flag} "<your prompt>"\`.`;
39
+ }
40
+ if (method === 'final-arg') {
41
+ return 'The prompt will be passed as the final command argument.';
42
+ }
43
+ return 'The prompt will be sent via standard input after the session command starts.';
44
+ };
45
+ export const preparePresetLaunch = (preset, prompt) => {
46
+ const baseArgs = injectTeammateMode(preset.command, preset.args || [], preset.detectionStrategy);
47
+ if (!prompt) {
48
+ return {
49
+ args: baseArgs,
50
+ method: getPromptInjectionMethod(preset),
51
+ };
52
+ }
53
+ switch (getPromptInjectionMethod(preset)) {
54
+ case 'flag':
55
+ return {
56
+ args: [...baseArgs, getPromptFlag(preset) || '--prompt', prompt],
57
+ method: 'flag',
58
+ };
59
+ case 'stdin':
60
+ return {
61
+ args: baseArgs,
62
+ method: 'stdin',
63
+ stdinPayload: `${prompt}\r`,
64
+ };
65
+ case 'final-arg':
66
+ return {
67
+ args: [...baseArgs, prompt],
68
+ method: 'final-arg',
69
+ };
70
+ }
71
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,167 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { describePromptInjection, getPromptInjectionMethod, preparePresetLaunch, } from './presetPrompt.js';
3
+ describe('presetPrompt', () => {
4
+ it('uses the final argument for claude presets', () => {
5
+ expect(preparePresetLaunch({ command: 'claude', args: ['--resume'], detectionStrategy: 'claude' }, 'fix the tests')).toEqual({
6
+ args: ['--resume', '--teammate-mode', 'in-process', 'fix the tests'],
7
+ method: 'final-arg',
8
+ });
9
+ });
10
+ it('uses the final argument for codex presets', () => {
11
+ expect(preparePresetLaunch({ command: 'codex', args: [], detectionStrategy: 'codex' }, 'refactor utils')).toEqual({
12
+ args: ['refactor utils'],
13
+ method: 'final-arg',
14
+ });
15
+ });
16
+ it('uses the final argument for cursor presets', () => {
17
+ expect(preparePresetLaunch({ command: 'agent', args: [], detectionStrategy: 'cursor' }, 'review code')).toEqual({
18
+ args: ['review code'],
19
+ method: 'final-arg',
20
+ });
21
+ });
22
+ it('uses the final argument for cline presets', () => {
23
+ expect(preparePresetLaunch({ command: 'cline', args: [], detectionStrategy: 'cline' }, 'fix bug')).toEqual({
24
+ args: ['fix bug'],
25
+ method: 'final-arg',
26
+ });
27
+ });
28
+ it('uses --prompt for opencode presets', () => {
29
+ expect(preparePresetLaunch({ command: 'opencode', args: ['run'], detectionStrategy: 'opencode' }, 'implement feature')).toEqual({
30
+ args: ['run', '--prompt', 'implement feature'],
31
+ method: 'flag',
32
+ });
33
+ });
34
+ it('uses -i for gemini presets', () => {
35
+ expect(preparePresetLaunch({ command: 'gemini', args: [], detectionStrategy: 'gemini' }, 'explain code')).toEqual({
36
+ args: ['-i', 'explain code'],
37
+ method: 'flag',
38
+ });
39
+ });
40
+ it('uses -i for github-copilot presets', () => {
41
+ expect(preparePresetLaunch({ command: 'copilot', args: [], detectionStrategy: 'github-copilot' }, 'create readme')).toEqual({
42
+ args: ['-i', 'create readme'],
43
+ method: 'flag',
44
+ });
45
+ });
46
+ it('uses -p for kimi presets', () => {
47
+ expect(preparePresetLaunch({ command: 'kimi', args: [], detectionStrategy: 'kimi' }, 'summarize')).toEqual({
48
+ args: ['-p', 'summarize'],
49
+ method: 'flag',
50
+ });
51
+ });
52
+ it('falls back to stdin for unknown commands', () => {
53
+ expect(preparePresetLaunch({ command: 'custom-agent', args: ['--interactive'] }, 'hello')).toEqual({
54
+ args: ['--interactive'],
55
+ method: 'stdin',
56
+ stdinPayload: 'hello\r',
57
+ });
58
+ });
59
+ describe('describePromptInjection', () => {
60
+ it('describes final-arg for claude', () => {
61
+ expect(describePromptInjection({
62
+ command: 'claude',
63
+ detectionStrategy: 'claude',
64
+ })).toContain('final command argument');
65
+ });
66
+ it('describes final-arg for codex', () => {
67
+ expect(describePromptInjection({
68
+ command: 'codex',
69
+ detectionStrategy: 'codex',
70
+ })).toContain('final command argument');
71
+ });
72
+ it('describes final-arg for cursor', () => {
73
+ expect(describePromptInjection({
74
+ command: 'agent',
75
+ detectionStrategy: 'cursor',
76
+ })).toContain('final command argument');
77
+ });
78
+ it('describes final-arg for cline', () => {
79
+ expect(describePromptInjection({
80
+ command: 'cline',
81
+ detectionStrategy: 'cline',
82
+ })).toContain('final command argument');
83
+ });
84
+ it('describes --prompt flag for opencode', () => {
85
+ expect(describePromptInjection({
86
+ command: 'opencode',
87
+ detectionStrategy: 'opencode',
88
+ })).toContain('--prompt');
89
+ });
90
+ it('describes -i flag for gemini', () => {
91
+ expect(describePromptInjection({
92
+ command: 'gemini',
93
+ detectionStrategy: 'gemini',
94
+ })).toContain('-i');
95
+ });
96
+ it('describes -i flag for github-copilot', () => {
97
+ expect(describePromptInjection({
98
+ command: 'copilot',
99
+ detectionStrategy: 'github-copilot',
100
+ })).toContain('-i');
101
+ });
102
+ it('describes -p flag for kimi', () => {
103
+ expect(describePromptInjection({
104
+ command: 'kimi',
105
+ detectionStrategy: 'kimi',
106
+ })).toContain('-p');
107
+ });
108
+ it('describes stdin for unknown strategy', () => {
109
+ expect(describePromptInjection({ command: 'custom-agent' })).toContain('standard input');
110
+ });
111
+ });
112
+ describe('getPromptInjectionMethod', () => {
113
+ it('returns final-arg for claude', () => {
114
+ expect(getPromptInjectionMethod({
115
+ command: 'claude',
116
+ detectionStrategy: 'claude',
117
+ })).toBe('final-arg');
118
+ });
119
+ it('returns final-arg for codex', () => {
120
+ expect(getPromptInjectionMethod({
121
+ command: 'codex',
122
+ detectionStrategy: 'codex',
123
+ })).toBe('final-arg');
124
+ });
125
+ it('returns final-arg for cursor', () => {
126
+ expect(getPromptInjectionMethod({
127
+ command: 'agent',
128
+ detectionStrategy: 'cursor',
129
+ })).toBe('final-arg');
130
+ });
131
+ it('returns final-arg for cline', () => {
132
+ expect(getPromptInjectionMethod({
133
+ command: 'cline',
134
+ detectionStrategy: 'cline',
135
+ })).toBe('final-arg');
136
+ });
137
+ it('returns flag for opencode', () => {
138
+ expect(getPromptInjectionMethod({
139
+ command: 'opencode',
140
+ detectionStrategy: 'opencode',
141
+ })).toBe('flag');
142
+ });
143
+ it('returns flag for gemini', () => {
144
+ expect(getPromptInjectionMethod({
145
+ command: 'gemini',
146
+ detectionStrategy: 'gemini',
147
+ })).toBe('flag');
148
+ });
149
+ it('returns flag for github-copilot', () => {
150
+ expect(getPromptInjectionMethod({
151
+ command: 'copilot',
152
+ detectionStrategy: 'github-copilot',
153
+ })).toBe('flag');
154
+ });
155
+ it('returns flag for kimi', () => {
156
+ expect(getPromptInjectionMethod({
157
+ command: 'kimi',
158
+ detectionStrategy: 'kimi',
159
+ })).toBe('flag');
160
+ });
161
+ it('returns stdin when detectionStrategy is not set', () => {
162
+ expect(getPromptInjectionMethod({ command: 'claude' })).toBe('stdin');
163
+ expect(getPromptInjectionMethod({ command: 'opencode' })).toBe('stdin');
164
+ expect(getPromptInjectionMethod({ command: 'custom-agent' })).toBe('stdin');
165
+ });
166
+ });
167
+ });
@@ -2,7 +2,7 @@ import { Worktree, Session } from '../types/index.js';
2
2
  /**
3
3
  * Worktree item with formatted content for display.
4
4
  */
5
- interface WorktreeItem {
5
+ export interface WorktreeItem {
6
6
  worktree: Worktree;
7
7
  session?: Session;
8
8
  baseLabel: string;
@@ -39,4 +39,3 @@ export declare function calculateColumnPositions(items: WorktreeItem[]): {
39
39
  * Assembles the final worktree label with proper column alignment
40
40
  */
41
41
  export declare function assembleWorktreeLabel(item: WorktreeItem, columns: ReturnType<typeof calculateColumnPositions>): string;
42
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",
@@ -41,11 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "optionalDependencies": {
44
- "@kodaikabasawa/ccmanager-darwin-arm64": "3.9.0",
45
- "@kodaikabasawa/ccmanager-darwin-x64": "3.9.0",
46
- "@kodaikabasawa/ccmanager-linux-arm64": "3.9.0",
47
- "@kodaikabasawa/ccmanager-linux-x64": "3.9.0",
48
- "@kodaikabasawa/ccmanager-win32-x64": "3.9.0"
44
+ "@kodaikabasawa/ccmanager-darwin-arm64": "3.11.0",
45
+ "@kodaikabasawa/ccmanager-darwin-x64": "3.11.0",
46
+ "@kodaikabasawa/ccmanager-linux-arm64": "3.11.0",
47
+ "@kodaikabasawa/ccmanager-linux-x64": "3.11.0",
48
+ "@kodaikabasawa/ccmanager-win32-x64": "3.11.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.28.0",
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import { GitProject } from '../types/index.js';
3
- interface ProjectListProps {
4
- projectsDir: string;
5
- onSelectProject: (project: GitProject) => void;
6
- error: string | null;
7
- onDismissError: () => void;
8
- }
9
- declare const ProjectList: React.FC<ProjectListProps>;
10
- export default ProjectList;