ccmanager 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/app.d.ts +6 -0
- package/dist/app.js +57 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +24 -0
- package/dist/components/App.d.ts +3 -0
- package/dist/components/App.js +228 -0
- package/dist/components/ConfigureShortcuts.d.ts +6 -0
- package/dist/components/ConfigureShortcuts.js +139 -0
- package/dist/components/Confirmation.d.ts +12 -0
- package/dist/components/Confirmation.js +42 -0
- package/dist/components/DeleteWorktree.d.ts +7 -0
- package/dist/components/DeleteWorktree.js +116 -0
- package/dist/components/Menu.d.ts +9 -0
- package/dist/components/Menu.js +154 -0
- package/dist/components/MergeWorktree.d.ts +7 -0
- package/dist/components/MergeWorktree.js +142 -0
- package/dist/components/NewWorktree.d.ts +7 -0
- package/dist/components/NewWorktree.js +49 -0
- package/dist/components/Session.d.ts +10 -0
- package/dist/components/Session.js +121 -0
- package/dist/constants/statusIcons.d.ts +18 -0
- package/dist/constants/statusIcons.js +27 -0
- package/dist/services/sessionManager.d.ts +16 -0
- package/dist/services/sessionManager.js +190 -0
- package/dist/services/sessionManager.test.d.ts +1 -0
- package/dist/services/sessionManager.test.js +99 -0
- package/dist/services/shortcutManager.d.ts +17 -0
- package/dist/services/shortcutManager.js +167 -0
- package/dist/services/worktreeService.d.ts +24 -0
- package/dist/services/worktreeService.js +220 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +21 -0
- package/dist/utils/promptDetector.d.ts +1 -0
- package/dist/utils/promptDetector.js +20 -0
- package/dist/utils/promptDetector.test.d.ts +1 -0
- package/dist/utils/promptDetector.test.js +81 -0
- package/package.json +70 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export class WorktreeService {
|
|
5
|
+
constructor(rootPath) {
|
|
6
|
+
Object.defineProperty(this, "rootPath", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: void 0
|
|
11
|
+
});
|
|
12
|
+
this.rootPath = rootPath || process.cwd();
|
|
13
|
+
}
|
|
14
|
+
getWorktrees() {
|
|
15
|
+
try {
|
|
16
|
+
const output = execSync('git worktree list --porcelain', {
|
|
17
|
+
cwd: this.rootPath,
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
});
|
|
20
|
+
const worktrees = [];
|
|
21
|
+
const lines = output.trim().split('\n');
|
|
22
|
+
let currentWorktree = {};
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (line.startsWith('worktree ')) {
|
|
25
|
+
if (currentWorktree.path) {
|
|
26
|
+
worktrees.push(currentWorktree);
|
|
27
|
+
}
|
|
28
|
+
currentWorktree = {
|
|
29
|
+
path: line.substring(9),
|
|
30
|
+
isMainWorktree: false,
|
|
31
|
+
hasSession: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
else if (line.startsWith('branch ')) {
|
|
35
|
+
currentWorktree.branch = line.substring(7);
|
|
36
|
+
}
|
|
37
|
+
else if (line === 'bare') {
|
|
38
|
+
currentWorktree.isMainWorktree = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (currentWorktree.path) {
|
|
42
|
+
worktrees.push(currentWorktree);
|
|
43
|
+
}
|
|
44
|
+
// Mark the first worktree as main if none are marked
|
|
45
|
+
if (worktrees.length > 0 && !worktrees.some(w => w.isMainWorktree)) {
|
|
46
|
+
worktrees[0].isMainWorktree = true;
|
|
47
|
+
}
|
|
48
|
+
return worktrees;
|
|
49
|
+
}
|
|
50
|
+
catch (_error) {
|
|
51
|
+
// If git worktree command fails, assume we're in a regular git repo
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
path: this.rootPath,
|
|
55
|
+
branch: this.getCurrentBranch(),
|
|
56
|
+
isMainWorktree: true,
|
|
57
|
+
hasSession: false,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
getCurrentBranch() {
|
|
63
|
+
try {
|
|
64
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
65
|
+
cwd: this.rootPath,
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
}).trim();
|
|
68
|
+
return branch;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
isGitRepository() {
|
|
75
|
+
return existsSync(path.join(this.rootPath, '.git'));
|
|
76
|
+
}
|
|
77
|
+
createWorktree(worktreePath, branch) {
|
|
78
|
+
try {
|
|
79
|
+
// Check if branch exists
|
|
80
|
+
let branchExists = false;
|
|
81
|
+
try {
|
|
82
|
+
execSync(`git rev-parse --verify ${branch}`, {
|
|
83
|
+
cwd: this.rootPath,
|
|
84
|
+
encoding: 'utf8',
|
|
85
|
+
});
|
|
86
|
+
branchExists = true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Branch doesn't exist
|
|
90
|
+
}
|
|
91
|
+
// Create the worktree
|
|
92
|
+
const command = branchExists
|
|
93
|
+
? `git worktree add "${worktreePath}" "${branch}"`
|
|
94
|
+
: `git worktree add -b "${branch}" "${worktreePath}"`;
|
|
95
|
+
execSync(command, {
|
|
96
|
+
cwd: this.rootPath,
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
});
|
|
99
|
+
return { success: true };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: error instanceof Error ? error.message : 'Failed to create worktree',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
deleteWorktree(worktreePath) {
|
|
109
|
+
try {
|
|
110
|
+
// Get the worktree info to find the branch
|
|
111
|
+
const worktrees = this.getWorktrees();
|
|
112
|
+
const worktree = worktrees.find(wt => wt.path === worktreePath);
|
|
113
|
+
if (!worktree) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: 'Worktree not found',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (worktree.isMainWorktree) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: 'Cannot delete the main worktree',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Remove the worktree
|
|
126
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
127
|
+
cwd: this.rootPath,
|
|
128
|
+
encoding: 'utf8',
|
|
129
|
+
});
|
|
130
|
+
// Delete the branch if it exists
|
|
131
|
+
const branchName = worktree.branch.replace('refs/heads/', '');
|
|
132
|
+
try {
|
|
133
|
+
execSync(`git branch -D "${branchName}"`, {
|
|
134
|
+
cwd: this.rootPath,
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Branch might not exist or might be checked out elsewhere
|
|
140
|
+
// This is not a fatal error
|
|
141
|
+
}
|
|
142
|
+
return { success: true };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: error instanceof Error ? error.message : 'Failed to delete worktree',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
mergeWorktree(sourceBranch, targetBranch, useRebase = false) {
|
|
152
|
+
try {
|
|
153
|
+
// Get worktrees to find the target worktree path
|
|
154
|
+
const worktrees = this.getWorktrees();
|
|
155
|
+
const targetWorktree = worktrees.find(wt => wt.branch.replace('refs/heads/', '') === targetBranch);
|
|
156
|
+
if (!targetWorktree) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: 'Target branch worktree not found',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Perform the merge or rebase in the target worktree
|
|
163
|
+
if (useRebase) {
|
|
164
|
+
// For rebase, we need to checkout source branch and rebase it onto target
|
|
165
|
+
const sourceWorktree = worktrees.find(wt => wt.branch.replace('refs/heads/', '') === sourceBranch);
|
|
166
|
+
if (!sourceWorktree) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
error: 'Source branch worktree not found',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// Rebase source branch onto target branch
|
|
173
|
+
execSync(`git rebase "${targetBranch}"`, {
|
|
174
|
+
cwd: sourceWorktree.path,
|
|
175
|
+
encoding: 'utf8',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Regular merge
|
|
180
|
+
execSync(`git merge --no-ff "${sourceBranch}"`, {
|
|
181
|
+
cwd: targetWorktree.path,
|
|
182
|
+
encoding: 'utf8',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return { success: true };
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: error instanceof Error
|
|
191
|
+
? error.message
|
|
192
|
+
: useRebase
|
|
193
|
+
? 'Failed to rebase branches'
|
|
194
|
+
: 'Failed to merge branches',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
deleteWorktreeByBranch(branch) {
|
|
199
|
+
try {
|
|
200
|
+
// Get worktrees to find the worktree by branch
|
|
201
|
+
const worktrees = this.getWorktrees();
|
|
202
|
+
const worktree = worktrees.find(wt => wt.branch.replace('refs/heads/', '') === branch);
|
|
203
|
+
if (!worktree) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
error: 'Worktree not found for branch',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return this.deleteWorktree(worktree.path);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: error instanceof Error
|
|
215
|
+
? error.message
|
|
216
|
+
: 'Failed to delete worktree by branch',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IPty } from 'node-pty';
|
|
2
|
+
export type SessionState = 'idle' | 'busy' | 'waiting_input';
|
|
3
|
+
export interface Worktree {
|
|
4
|
+
path: string;
|
|
5
|
+
branch: string;
|
|
6
|
+
isMainWorktree: boolean;
|
|
7
|
+
hasSession: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface Session {
|
|
10
|
+
id: string;
|
|
11
|
+
worktreePath: string;
|
|
12
|
+
process: IPty;
|
|
13
|
+
state: SessionState;
|
|
14
|
+
output: string[];
|
|
15
|
+
outputHistory: Buffer[];
|
|
16
|
+
lastActivity: Date;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface SessionManager {
|
|
20
|
+
sessions: Map<string, Session>;
|
|
21
|
+
createSession(worktreePath: string): Session;
|
|
22
|
+
getSession(worktreePath: string): Session | undefined;
|
|
23
|
+
destroySession(worktreePath: string): void;
|
|
24
|
+
getAllSessions(): Session[];
|
|
25
|
+
}
|
|
26
|
+
export interface ShortcutKey {
|
|
27
|
+
ctrl?: boolean;
|
|
28
|
+
alt?: boolean;
|
|
29
|
+
shift?: boolean;
|
|
30
|
+
key: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ShortcutConfig {
|
|
33
|
+
returnToMenu: ShortcutKey;
|
|
34
|
+
cancel: ShortcutKey;
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_SHORTCUTS: ShortcutConfig;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const log: {
|
|
2
|
+
log: (...args: unknown[]) => void;
|
|
3
|
+
info: (...args: unknown[]) => void;
|
|
4
|
+
warn: (...args: unknown[]) => void;
|
|
5
|
+
error: (...args: unknown[]) => void;
|
|
6
|
+
debug: (...args: unknown[]) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const logger: {
|
|
9
|
+
log: (...args: unknown[]) => void;
|
|
10
|
+
info: (...args: unknown[]) => void;
|
|
11
|
+
warn: (...args: unknown[]) => void;
|
|
12
|
+
error: (...args: unknown[]) => void;
|
|
13
|
+
debug: (...args: unknown[]) => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { format } from 'util';
|
|
4
|
+
const LOG_FILE = path.join(process.cwd(), 'ccmanager.log');
|
|
5
|
+
// Clear log file on startup
|
|
6
|
+
fs.writeFileSync(LOG_FILE, '', 'utf8');
|
|
7
|
+
function writeLog(level, args) {
|
|
8
|
+
const timestamp = new Date().toISOString();
|
|
9
|
+
const message = format(...args);
|
|
10
|
+
const logLine = `[${timestamp}] [${level}] ${message}\n`;
|
|
11
|
+
fs.appendFileSync(LOG_FILE, logLine, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
export const log = {
|
|
14
|
+
log: (...args) => writeLog('LOG', args),
|
|
15
|
+
info: (...args) => writeLog('INFO', args),
|
|
16
|
+
warn: (...args) => writeLog('WARN', args),
|
|
17
|
+
error: (...args) => writeLog('ERROR', args),
|
|
18
|
+
debug: (...args) => writeLog('DEBUG', args),
|
|
19
|
+
};
|
|
20
|
+
// Alias for console.log style usage
|
|
21
|
+
export const logger = log;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function includesPromptBoxBottomBorder(output: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function includesPromptBoxBottomBorder(output) {
|
|
2
|
+
// Check if the output includes a prompt box bottom border
|
|
3
|
+
return output
|
|
4
|
+
.trim()
|
|
5
|
+
.split('\n')
|
|
6
|
+
.some(line => {
|
|
7
|
+
// Accept patterns:
|
|
8
|
+
// - `──╯` (ends with ╯)
|
|
9
|
+
// - `╰───╯` (starts with ╰ and ends with ╯)
|
|
10
|
+
// Reject if:
|
|
11
|
+
// - vertical line exists after ╯
|
|
12
|
+
// - line starts with ╰ but doesn't end with ╯
|
|
13
|
+
// Check if line ends with ╯ but not followed by │
|
|
14
|
+
if (line.endsWith('╯') && !line.includes('╯ │')) {
|
|
15
|
+
// Accept if it's just ──╯ or ╰───╯ pattern
|
|
16
|
+
return /─+╯$/.test(line) || /^╰─+╯$/.test(line);
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { includesPromptBoxBottomBorder } from './promptDetector.js';
|
|
3
|
+
describe('includesPromptBoxBottomBorder', () => {
|
|
4
|
+
it('should return false for empty output', () => {
|
|
5
|
+
expect(includesPromptBoxBottomBorder('')).toBe(false);
|
|
6
|
+
expect(includesPromptBoxBottomBorder(' ')).toBe(false);
|
|
7
|
+
expect(includesPromptBoxBottomBorder('\n\n')).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
it('should accept lines ending with ╯', () => {
|
|
10
|
+
// Basic pattern
|
|
11
|
+
expect(includesPromptBoxBottomBorder('──╯')).toBe(true);
|
|
12
|
+
expect(includesPromptBoxBottomBorder('────────╯')).toBe(true);
|
|
13
|
+
expect(includesPromptBoxBottomBorder('─╯')).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('should accept complete bottom border (╰───╯)', () => {
|
|
16
|
+
expect(includesPromptBoxBottomBorder('╰───╯')).toBe(true);
|
|
17
|
+
expect(includesPromptBoxBottomBorder('╰─────────────╯')).toBe(true);
|
|
18
|
+
expect(includesPromptBoxBottomBorder('╰─╯')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('should accept when part of multi-line output', () => {
|
|
21
|
+
const output1 = `Some text
|
|
22
|
+
──╯
|
|
23
|
+
More text`;
|
|
24
|
+
expect(includesPromptBoxBottomBorder(output1)).toBe(true);
|
|
25
|
+
const output2 = `First line
|
|
26
|
+
╰─────────────╯
|
|
27
|
+
Last line`;
|
|
28
|
+
expect(includesPromptBoxBottomBorder(output2)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('should accept with leading/trailing whitespace', () => {
|
|
31
|
+
expect(includesPromptBoxBottomBorder(' ──╯ ')).toBe(true);
|
|
32
|
+
expect(includesPromptBoxBottomBorder('\t╰───╯\t')).toBe(true);
|
|
33
|
+
expect(includesPromptBoxBottomBorder('\n──╯\n')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('should reject when ╯ is followed by │', () => {
|
|
36
|
+
expect(includesPromptBoxBottomBorder('──╯ │')).toBe(false);
|
|
37
|
+
expect(includesPromptBoxBottomBorder('╰───╯ │')).toBe(false);
|
|
38
|
+
expect(includesPromptBoxBottomBorder('──╯ │ more text')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('should reject when line starts with ╰ but does not end with ╯', () => {
|
|
41
|
+
expect(includesPromptBoxBottomBorder('╰───')).toBe(false);
|
|
42
|
+
expect(includesPromptBoxBottomBorder('╰─────────')).toBe(false);
|
|
43
|
+
expect(includesPromptBoxBottomBorder('╰─── some text')).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it('should reject lines that do not match the pattern', () => {
|
|
46
|
+
// Missing ─ characters
|
|
47
|
+
expect(includesPromptBoxBottomBorder('╯')).toBe(false);
|
|
48
|
+
expect(includesPromptBoxBottomBorder('╰╯')).toBe(false);
|
|
49
|
+
// Wrong characters
|
|
50
|
+
expect(includesPromptBoxBottomBorder('===╯')).toBe(false);
|
|
51
|
+
expect(includesPromptBoxBottomBorder('╰===╯')).toBe(false);
|
|
52
|
+
expect(includesPromptBoxBottomBorder('---╯')).toBe(false);
|
|
53
|
+
// Top border pattern
|
|
54
|
+
expect(includesPromptBoxBottomBorder('╭───╮')).toBe(false);
|
|
55
|
+
// Middle line pattern
|
|
56
|
+
expect(includesPromptBoxBottomBorder('│ > │')).toBe(false);
|
|
57
|
+
// Random text
|
|
58
|
+
expect(includesPromptBoxBottomBorder('Some random text')).toBe(false);
|
|
59
|
+
expect(includesPromptBoxBottomBorder('Exit code: 0')).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it('should handle complex multi-line scenarios correctly', () => {
|
|
62
|
+
const validOutput = `
|
|
63
|
+
╭────────────────────╮
|
|
64
|
+
│ > hello │
|
|
65
|
+
╰────────────────────╯
|
|
66
|
+
Some status text`;
|
|
67
|
+
expect(includesPromptBoxBottomBorder(validOutput)).toBe(true);
|
|
68
|
+
const invalidOutput = `
|
|
69
|
+
╭────────────────────╮
|
|
70
|
+
│ > hello │
|
|
71
|
+
╰────────────────────
|
|
72
|
+
Some other text`;
|
|
73
|
+
expect(includesPromptBoxBottomBorder(invalidOutput)).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('should handle partial border at end of line', () => {
|
|
76
|
+
const partialBorder = `Some output text ──╯`;
|
|
77
|
+
expect(includesPromptBoxBottomBorder(partialBorder)).toBe(true);
|
|
78
|
+
const partialInvalid = `Some output text ──╯ │`;
|
|
79
|
+
expect(includesPromptBoxBottomBorder(partialInvalid)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccmanager",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Kodai Kabasawa",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/kbwo/ccmanager.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude",
|
|
13
|
+
"code",
|
|
14
|
+
"worktree",
|
|
15
|
+
"git",
|
|
16
|
+
"tui",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"ccmanager": "dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"start": "node dist/cli.js",
|
|
30
|
+
"test": "vitest",
|
|
31
|
+
"test:run": "vitest run",
|
|
32
|
+
"lint": "eslint src",
|
|
33
|
+
"lint:fix": "eslint src --fix",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm run test:run && npm run build",
|
|
36
|
+
"prepare": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"ink": "^4.1.0",
|
|
43
|
+
"ink-select-input": "^5.0.0",
|
|
44
|
+
"ink-text-input": "^5.0.1",
|
|
45
|
+
"meow": "^11.0.0",
|
|
46
|
+
"node-pty": "^1.0.0",
|
|
47
|
+
"react": "^18.2.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@eslint/js": "^9.28.0",
|
|
51
|
+
"@sindresorhus/tsconfig": "^3.0.1",
|
|
52
|
+
"@types/node": "^20.0.0",
|
|
53
|
+
"@types/react": "^18.0.32",
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
|
55
|
+
"@typescript-eslint/parser": "^8.33.1",
|
|
56
|
+
"@vdemedes/prettier-config": "^2.0.1",
|
|
57
|
+
"chalk": "^5.2.0",
|
|
58
|
+
"eslint": "^9.28.0",
|
|
59
|
+
"eslint-config-prettier": "^10.1.5",
|
|
60
|
+
"eslint-plugin-prettier": "^5.4.1",
|
|
61
|
+
"eslint-plugin-react": "^7.32.2",
|
|
62
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
63
|
+
"ink-testing-library": "^3.0.0",
|
|
64
|
+
"prettier": "^3.0.0",
|
|
65
|
+
"ts-node": "^10.9.1",
|
|
66
|
+
"typescript": "^5.0.3",
|
|
67
|
+
"vitest": "^3.2.2"
|
|
68
|
+
},
|
|
69
|
+
"prettier": "@vdemedes/prettier-config"
|
|
70
|
+
}
|