ccmanager 4.3.1 → 4.4.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/README.md +54 -0
- package/dist/components/App.js +92 -30
- package/dist/components/App.test.js +86 -0
- package/dist/components/Dashboard.js +10 -3
- package/dist/components/DeleteWorktree.js +2 -13
- package/dist/components/Menu.js +28 -17
- package/dist/components/Menu.test.js +37 -1
- package/dist/components/RestoreSessions.d.ts +19 -0
- package/dist/components/RestoreSessions.js +28 -0
- package/dist/components/SessionActions.d.ts +17 -2
- package/dist/components/SessionActions.js +19 -17
- package/dist/components/SessionActions.test.d.ts +1 -0
- package/dist/components/SessionActions.test.js +94 -0
- package/dist/hooks/useAvailableLabelWidth.d.ts +6 -0
- package/dist/hooks/useAvailableLabelWidth.js +15 -0
- package/dist/services/config/globalConfigManager.js +3 -12
- package/dist/services/globalSessionOrchestrator.d.ts +7 -0
- package/dist/services/globalSessionOrchestrator.js +40 -0
- package/dist/services/globalSessionOrchestrator.restore.test.d.ts +1 -0
- package/dist/services/globalSessionOrchestrator.restore.test.js +76 -0
- package/dist/services/projectManager.js +3 -11
- package/dist/services/sessionManager.d.ts +6 -0
- package/dist/services/sessionManager.js +16 -0
- package/dist/services/sessionRestoreStore.d.ts +75 -0
- package/dist/services/sessionRestoreStore.js +138 -0
- package/dist/services/sessionRestoreStore.test.d.ts +1 -0
- package/dist/services/sessionRestoreStore.test.js +92 -0
- package/dist/services/sessionRestorer.d.ts +46 -0
- package/dist/services/sessionRestorer.js +123 -0
- package/dist/services/sessionRestorer.test.d.ts +1 -0
- package/dist/services/sessionRestorer.test.js +163 -0
- package/dist/services/worktreeService.d.ts +12 -0
- package/dist/services/worktreeService.js +30 -0
- package/dist/services/worktreeService.test.js +55 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/utils/configDir.d.ts +10 -0
- package/dist/utils/configDir.js +31 -0
- package/dist/utils/errorMessage.d.ts +7 -0
- package/dist/utils/errorMessage.js +19 -0
- package/dist/utils/filterByQuery.test.js +2 -0
- package/dist/utils/gitUtils.d.ts +1 -0
- package/dist/utils/gitUtils.js +15 -0
- package/dist/utils/hookExecutor.test.js +4 -0
- package/dist/utils/worktreeInclude.d.ts +33 -0
- package/dist/utils/worktreeInclude.js +100 -0
- package/dist/utils/worktreeInclude.test.d.ts +1 -0
- package/dist/utils/worktreeInclude.test.js +91 -0
- package/dist/utils/worktreeUtils.d.ts +38 -4
- package/dist/utils/worktreeUtils.js +76 -17
- package/dist/utils/worktreeUtils.test.js +82 -5
- package/package.json +6 -6
|
@@ -206,7 +206,7 @@ function gitStatusColumns(wt, fullBranchName) {
|
|
|
206
206
|
function buildSessionItem(wt, session, sessionSuffix) {
|
|
207
207
|
const stateData = session?.stateMutex.getSnapshot();
|
|
208
208
|
const status = stateData
|
|
209
|
-
? `
|
|
209
|
+
? `[${getStatusDisplay(stateData.state, stateData.backgroundTaskCount, stateData.teamMemberCount)}]`
|
|
210
210
|
: '';
|
|
211
211
|
const fullBranchName = wt.branch
|
|
212
212
|
? wt.branch.replace('refs/heads/', '')
|
|
@@ -214,7 +214,7 @@ function buildSessionItem(wt, session, sessionSuffix) {
|
|
|
214
214
|
const branchName = truncateString(fullBranchName, MAX_BRANCH_NAME_LENGTH);
|
|
215
215
|
const isMain = wt.isMainWorktree ? ' (main)' : '';
|
|
216
216
|
const { displaySuffix: dirSuffix, rawName: rawDirName } = formatWorktreeDirectorySuffix(wt, fullBranchName);
|
|
217
|
-
const baseLabel = `${branchName}${dirSuffix}${isMain}${sessionSuffix}
|
|
217
|
+
const baseLabel = `${branchName}${dirSuffix}${isMain}${sessionSuffix}`;
|
|
218
218
|
// Use the full (untruncated) branch name so search still matches the tail
|
|
219
219
|
// of long branch names; status icons are excluded so they don't match.
|
|
220
220
|
const rawDirForSearch = rawDirName ? ` @ ${rawDirName}` : '';
|
|
@@ -227,6 +227,7 @@ function buildSessionItem(wt, session, sessionSuffix) {
|
|
|
227
227
|
worktree: wt,
|
|
228
228
|
session,
|
|
229
229
|
baseLabel,
|
|
230
|
+
status,
|
|
230
231
|
searchableName,
|
|
231
232
|
fileChanges,
|
|
232
233
|
aheadBehind,
|
|
@@ -235,6 +236,7 @@ function buildSessionItem(wt, session, sessionSuffix) {
|
|
|
235
236
|
error,
|
|
236
237
|
lengths: {
|
|
237
238
|
base: stripAnsi(baseLabel).length,
|
|
239
|
+
status: stripAnsi(status).length,
|
|
238
240
|
fileChanges: stripAnsi(fileChanges).length,
|
|
239
241
|
aheadBehind: stripAnsi(aheadBehind).length,
|
|
240
242
|
parentBranch: stripAnsi(parentBranch).length,
|
|
@@ -277,35 +279,59 @@ export function prepareSessionItems(worktrees, sessions, options) {
|
|
|
277
279
|
}
|
|
278
280
|
return items;
|
|
279
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Visible width of the name portion when the state tag is appended to it
|
|
284
|
+
* instead of getting its own column.
|
|
285
|
+
*/
|
|
286
|
+
function inlineBaseLength(item) {
|
|
287
|
+
return (item.lengths.base + (item.lengths.status ? item.lengths.status + 1 : 0));
|
|
288
|
+
}
|
|
280
289
|
/**
|
|
281
290
|
* Calculates column positions based on content widths.
|
|
291
|
+
*
|
|
292
|
+
* `availableWidth` is the number of terminal columns the label may occupy
|
|
293
|
+
* (i.e. terminal width minus whatever prefix the caller prepends). When the
|
|
294
|
+
* aligned-status layout would not fit in it, the layout falls back to appending
|
|
295
|
+
* the state tag to the name, which is narrower. Omit it to always align.
|
|
282
296
|
*/
|
|
283
|
-
export function calculateColumnPositions(items) {
|
|
297
|
+
export function calculateColumnPositions(items, availableWidth) {
|
|
284
298
|
// Calculate maximum widths from pre-calculated lengths
|
|
285
299
|
let maxBranchLength = 0;
|
|
300
|
+
let maxInlineBranchLength = 0;
|
|
301
|
+
let maxStatusLength = 0;
|
|
286
302
|
let maxFileChangesLength = 0;
|
|
287
303
|
let maxAheadBehindLength = 0;
|
|
288
304
|
let maxParentBranchLength = 0;
|
|
305
|
+
let maxLastCommitDateLength = 0;
|
|
289
306
|
items.forEach(item => {
|
|
290
307
|
// Skip items with errors for alignment calculation
|
|
291
308
|
if (item.error)
|
|
292
309
|
return;
|
|
293
310
|
maxBranchLength = Math.max(maxBranchLength, item.lengths.base);
|
|
311
|
+
maxInlineBranchLength = Math.max(maxInlineBranchLength, inlineBaseLength(item));
|
|
312
|
+
maxStatusLength = Math.max(maxStatusLength, item.lengths.status);
|
|
294
313
|
maxFileChangesLength = Math.max(maxFileChangesLength, item.lengths.fileChanges);
|
|
295
314
|
maxAheadBehindLength = Math.max(maxAheadBehindLength, item.lengths.aheadBehind);
|
|
296
315
|
maxParentBranchLength = Math.max(maxParentBranchLength, item.lengths.parentBranch);
|
|
316
|
+
maxLastCommitDateLength = Math.max(maxLastCommitDateLength, item.lengths.lastCommitDate);
|
|
297
317
|
});
|
|
298
|
-
// Simple column positioning
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
parentBranch
|
|
307
|
-
lastCommitDate: lastCommitDateColumn,
|
|
318
|
+
// Simple column positioning. `branchWidth` is how much room the name portion
|
|
319
|
+
// gets, `statusWidth` is 0 when the state tag rides along with the name.
|
|
320
|
+
const layout = (branchWidth, statusWidth) => {
|
|
321
|
+
const fileChanges = branchWidth + MIN_COLUMN_PADDING;
|
|
322
|
+
const aheadBehind = fileChanges + maxFileChangesLength + MIN_COLUMN_PADDING + 2;
|
|
323
|
+
const parentBranch = aheadBehind + maxAheadBehindLength + MIN_COLUMN_PADDING + 2;
|
|
324
|
+
const status = parentBranch + maxParentBranchLength + MIN_COLUMN_PADDING + 2;
|
|
325
|
+
const lastCommitDate = status + (statusWidth ? statusWidth + MIN_COLUMN_PADDING : 0);
|
|
326
|
+
return { fileChanges, aheadBehind, parentBranch, status, lastCommitDate };
|
|
308
327
|
};
|
|
328
|
+
const aligned = layout(maxBranchLength, maxStatusLength);
|
|
329
|
+
const fits = availableWidth === undefined ||
|
|
330
|
+
aligned.lastCommitDate + maxLastCommitDateLength <= availableWidth;
|
|
331
|
+
if (fits) {
|
|
332
|
+
return { ...aligned, alignStatus: true };
|
|
333
|
+
}
|
|
334
|
+
return { ...layout(maxInlineBranchLength, 0), alignStatus: false };
|
|
309
335
|
}
|
|
310
336
|
// Pad string to column position
|
|
311
337
|
function padTo(str, visibleLength, column) {
|
|
@@ -315,12 +341,18 @@ function padTo(str, visibleLength, column) {
|
|
|
315
341
|
* Assembles the final worktree label with proper column alignment
|
|
316
342
|
*/
|
|
317
343
|
export function assembleSessionLabel(item, columns) {
|
|
318
|
-
|
|
344
|
+
const inlineStatus = item.status
|
|
345
|
+
? `${item.baseLabel} ${item.status}`
|
|
346
|
+
: item.baseLabel;
|
|
347
|
+
// If there's an error, just show the base label with error appended.
|
|
348
|
+
// Error rows carry no columns at all, so the state tag stays next to the name.
|
|
319
349
|
if (item.error) {
|
|
320
|
-
return `${
|
|
350
|
+
return `${inlineStatus} ${item.error}`;
|
|
321
351
|
}
|
|
322
|
-
let label = item.baseLabel;
|
|
323
|
-
let currentLength =
|
|
352
|
+
let label = columns.alignStatus ? item.baseLabel : inlineStatus;
|
|
353
|
+
let currentLength = columns.alignStatus
|
|
354
|
+
? item.lengths.base
|
|
355
|
+
: inlineBaseLength(item);
|
|
324
356
|
if (item.fileChanges) {
|
|
325
357
|
label = padTo(label, currentLength, columns.fileChanges) + item.fileChanges;
|
|
326
358
|
currentLength = columns.fileChanges + item.lengths.fileChanges;
|
|
@@ -334,9 +366,36 @@ export function assembleSessionLabel(item, columns) {
|
|
|
334
366
|
padTo(label, currentLength, columns.parentBranch) + item.parentBranch;
|
|
335
367
|
currentLength = columns.parentBranch + item.lengths.parentBranch;
|
|
336
368
|
}
|
|
369
|
+
if (columns.alignStatus && item.status) {
|
|
370
|
+
label = padTo(label, currentLength, columns.status) + item.status;
|
|
371
|
+
currentLength = columns.status + item.lengths.status;
|
|
372
|
+
}
|
|
337
373
|
if (item.lastCommitDate) {
|
|
338
374
|
label =
|
|
339
375
|
padTo(label, currentLength, columns.lastCommitDate) + item.lastCommitDate;
|
|
340
376
|
}
|
|
341
377
|
return label;
|
|
342
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Whether a worktree may be deleted by CCManager.
|
|
381
|
+
*
|
|
382
|
+
* Two worktrees are off limits:
|
|
383
|
+
* - the main worktree, because git refuses to remove it and the repository
|
|
384
|
+
* would be left without a checkout;
|
|
385
|
+
* - the worktree that contains the current working directory, because removing
|
|
386
|
+
* the directory CCManager is running in breaks the running process.
|
|
387
|
+
*
|
|
388
|
+
* Single source of truth for the rule, shared by the multi-select delete screen
|
|
389
|
+
* and the per-row delete action.
|
|
390
|
+
*/
|
|
391
|
+
export function isDeletableWorktree(worktree, cwd = process.cwd()) {
|
|
392
|
+
if (worktree.isMainWorktree)
|
|
393
|
+
return false;
|
|
394
|
+
const resolvedCwd = path.resolve(cwd);
|
|
395
|
+
const resolvedPath = path.resolve(worktree.path);
|
|
396
|
+
if (resolvedCwd === resolvedPath ||
|
|
397
|
+
resolvedCwd.startsWith(resolvedPath + path.sep)) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { generateWorktreeDirectory, extractBranchParts, truncateString, prepareSessionItems, calculateColumnPositions, assembleSessionLabel, } from './worktreeUtils.js';
|
|
2
|
+
import { generateWorktreeDirectory, extractBranchParts, truncateString, prepareSessionItems, calculateColumnPositions, assembleSessionLabel, isDeletableWorktree, } from './worktreeUtils.js';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import { Mutex, createInitialSessionStateData } from './mutex.js';
|
|
5
5
|
import { createStateDetector } from '../services/stateDetector/index.js';
|
|
@@ -139,6 +139,7 @@ describe('prepareSessionItems', () => {
|
|
|
139
139
|
stateCheckInterval: undefined,
|
|
140
140
|
isPrimaryCommand: true,
|
|
141
141
|
presetName: undefined,
|
|
142
|
+
presetId: undefined,
|
|
142
143
|
detectionStrategy: 'claude',
|
|
143
144
|
devcontainerConfig: undefined,
|
|
144
145
|
stateMutex: new Mutex({
|
|
@@ -152,9 +153,12 @@ describe('prepareSessionItems', () => {
|
|
|
152
153
|
expect(items).toHaveLength(1);
|
|
153
154
|
expect(items[0]?.baseLabel).toBe('feature/test-branch');
|
|
154
155
|
});
|
|
155
|
-
it('should
|
|
156
|
+
it('should expose the session status separately from the name', () => {
|
|
156
157
|
const items = prepareSessionItems([mockWorktree], [mockSession]);
|
|
157
|
-
|
|
158
|
+
// The status tag is its own field so it can be rendered as an aligned
|
|
159
|
+
// column; it must not be baked into the name portion.
|
|
160
|
+
expect(items[0]?.status).toBe('[○ Idle]');
|
|
161
|
+
expect(items[0]?.baseLabel).toBe('feature/test-branch');
|
|
158
162
|
});
|
|
159
163
|
it('should mark main worktree', () => {
|
|
160
164
|
const mainWorktree = { ...mockWorktree, isMainWorktree: true };
|
|
@@ -249,8 +253,9 @@ describe('prepareSessionItems', () => {
|
|
|
249
253
|
sessionName: 'lab',
|
|
250
254
|
},
|
|
251
255
|
]);
|
|
252
|
-
// Order must be: branch, dir suffix, (no main), session suffix
|
|
253
|
-
expect(items[0]?.baseLabel).
|
|
256
|
+
// Order must be: branch, dir suffix, (no main), session suffix.
|
|
257
|
+
expect(items[0]?.baseLabel).toBe('feature/foo @ foo-api: lab');
|
|
258
|
+
expect(items[0]?.status).toMatch(/^\[.*Idle.*\]$/);
|
|
254
259
|
});
|
|
255
260
|
it('does not break column alignment when a dir suffix is appended', () => {
|
|
256
261
|
const items = prepareSessionItems([
|
|
@@ -277,6 +282,7 @@ describe('column alignment', () => {
|
|
|
277
282
|
{
|
|
278
283
|
worktree: {},
|
|
279
284
|
baseLabel: 'feature/test-branch',
|
|
285
|
+
status: '',
|
|
280
286
|
searchableName: 'feature/test-branch',
|
|
281
287
|
fileChanges: '\x1b[32m+10\x1b[0m \x1b[31m-5\x1b[0m',
|
|
282
288
|
aheadBehind: '\x1b[33m↑2 ↓3\x1b[0m',
|
|
@@ -284,6 +290,7 @@ describe('column alignment', () => {
|
|
|
284
290
|
lastCommitDate: '',
|
|
285
291
|
lengths: {
|
|
286
292
|
base: 19, // 'feature/test-branch'.length
|
|
293
|
+
status: 0,
|
|
287
294
|
fileChanges: 6, // '+10 -5'.length
|
|
288
295
|
aheadBehind: 5, // '↑2 ↓3'.length
|
|
289
296
|
parentBranch: 0,
|
|
@@ -293,6 +300,7 @@ describe('column alignment', () => {
|
|
|
293
300
|
{
|
|
294
301
|
worktree: {},
|
|
295
302
|
baseLabel: 'main',
|
|
303
|
+
status: '',
|
|
296
304
|
searchableName: 'main',
|
|
297
305
|
fileChanges: '\x1b[32m+2\x1b[0m \x1b[31m-1\x1b[0m',
|
|
298
306
|
aheadBehind: '\x1b[33m↑1\x1b[0m',
|
|
@@ -300,6 +308,7 @@ describe('column alignment', () => {
|
|
|
300
308
|
lastCommitDate: '',
|
|
301
309
|
lengths: {
|
|
302
310
|
base: 4, // 'main'.length
|
|
311
|
+
status: 0,
|
|
303
312
|
fileChanges: 5, // '+2 -1'.length
|
|
304
313
|
aheadBehind: 2, // '↑1'.length
|
|
305
314
|
parentBranch: 0,
|
|
@@ -325,3 +334,71 @@ describe('column alignment', () => {
|
|
|
325
334
|
expect(plain.indexOf('+10 -5')).toBe(21); // Should start at column 21
|
|
326
335
|
});
|
|
327
336
|
});
|
|
337
|
+
describe('isDeletableWorktree', () => {
|
|
338
|
+
it('should reject the main worktree', () => {
|
|
339
|
+
expect(isDeletableWorktree({ path: '/repo', isMainWorktree: true }, '/somewhere/else')).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
it('should reject the worktree holding the current working directory', () => {
|
|
342
|
+
expect(isDeletableWorktree({ path: '/repo/worktrees/feature', isMainWorktree: false }, '/repo/worktrees/feature')).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
it('should reject a worktree that is an ancestor of the current working directory', () => {
|
|
345
|
+
expect(isDeletableWorktree({ path: '/repo/worktrees/feature', isMainWorktree: false }, '/repo/worktrees/feature/src/components')).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
it('should accept a sibling worktree with a shared path prefix', () => {
|
|
348
|
+
// '/repo/worktrees/feature-2' starts with the '/repo/worktrees/feature'
|
|
349
|
+
// string but is a different directory, so it stays deletable.
|
|
350
|
+
expect(isDeletableWorktree({ path: '/repo/worktrees/feature', isMainWorktree: false }, '/repo/worktrees/feature-2')).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
it('should accept an unrelated linked worktree', () => {
|
|
353
|
+
expect(isDeletableWorktree({ path: '/repo/worktrees/feature', isMainWorktree: false }, '/repo')).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
describe('session status column', () => {
|
|
357
|
+
const makeItem = (baseLabel, status, lastCommitDate) => ({
|
|
358
|
+
worktree: {},
|
|
359
|
+
baseLabel,
|
|
360
|
+
status,
|
|
361
|
+
searchableName: baseLabel,
|
|
362
|
+
fileChanges: '',
|
|
363
|
+
aheadBehind: '',
|
|
364
|
+
parentBranch: '',
|
|
365
|
+
lastCommitDate,
|
|
366
|
+
lengths: {
|
|
367
|
+
base: baseLabel.length,
|
|
368
|
+
status: status.length,
|
|
369
|
+
fileChanges: 0,
|
|
370
|
+
aheadBehind: 0,
|
|
371
|
+
parentBranch: 0,
|
|
372
|
+
lastCommitDate: lastCommitDate.length,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
const items = [
|
|
376
|
+
makeItem('feature/a-very-long-branch-name', '[○ Idle]', '1d ago'),
|
|
377
|
+
makeItem('main', '[● Busy]', '3w ago'),
|
|
378
|
+
];
|
|
379
|
+
it('starts every status tag at the same column, just left of the date', () => {
|
|
380
|
+
const columns = calculateColumnPositions(items, 120);
|
|
381
|
+
expect(columns.alignStatus).toBe(true);
|
|
382
|
+
const labels = items.map(item => assembleSessionLabel(item, columns));
|
|
383
|
+
for (const [index, label] of labels.entries()) {
|
|
384
|
+
expect(label.indexOf(items[index].status)).toBe(columns.status);
|
|
385
|
+
expect(label.indexOf(items[index].lastCommitDate)).toBe(columns.lastCommitDate);
|
|
386
|
+
}
|
|
387
|
+
// The gap between the tag and the date is only the column padding.
|
|
388
|
+
expect(columns.lastCommitDate - columns.status).toBe('[○ Idle]'.length + 2);
|
|
389
|
+
});
|
|
390
|
+
it('falls back to appending the status to the name when too narrow', () => {
|
|
391
|
+
const columns = calculateColumnPositions(items, 40);
|
|
392
|
+
expect(columns.alignStatus).toBe(false);
|
|
393
|
+
expect(assembleSessionLabel(items[0], columns)).toContain('feature/a-very-long-branch-name [○ Idle]');
|
|
394
|
+
expect(assembleSessionLabel(items[1], columns)).toContain('main [● Busy]');
|
|
395
|
+
});
|
|
396
|
+
it('keeps the status next to the name on rows showing a git error', () => {
|
|
397
|
+
const errored = {
|
|
398
|
+
...makeItem('main', '[○ Idle]', ''),
|
|
399
|
+
error: '[git error]',
|
|
400
|
+
};
|
|
401
|
+
const columns = calculateColumnPositions([...items, errored], 120);
|
|
402
|
+
expect(assembleSessionLabel(errored, columns)).toBe('main [○ Idle] [git error]');
|
|
403
|
+
});
|
|
404
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.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": "4.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "4.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "4.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "4.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "4.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "4.4.0",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "4.4.0",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "4.4.0",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "4.4.0",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "4.4.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|