yggtree 1.4.2 → 1.4.4
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 +160 -113
- package/dist/commands/wt/create-branch.js +41 -27
- package/dist/commands/wt/create-multi.js +11 -2
- package/dist/commands/wt/create-sandbox.js +3 -1
- package/dist/commands/wt/create.js +132 -51
- package/dist/commands/wt/enter.js +3 -27
- package/dist/commands/wt/handoff.js +7 -0
- package/dist/commands/wt/open.js +269 -60
- package/dist/index.js +149 -126
- package/dist/lib/env-files.js +72 -0
- package/dist/lib/git.js +5 -0
- package/dist/lib/repo-context.js +46 -0
- package/dist/lib/update-check.js +55 -0
- package/package.json +8 -4
- package/dist/commands/wt/close.js +0 -96
package/dist/commands/wt/open.js
CHANGED
|
@@ -9,20 +9,30 @@ import { log, ui } from '../../lib/ui.js';
|
|
|
9
9
|
import { ensureAutocompletePrompt } from '../../lib/prompt.js';
|
|
10
10
|
import { detectWorktreeType, findWorktreeByName, formatWorktreeDisplayPath, formatWorktreeType, getWorktreeBranchName, } from '../../lib/worktree.js';
|
|
11
11
|
import { enterCommand } from './enter.js';
|
|
12
|
-
const OPEN_TOOL_CANDIDATES = [
|
|
13
|
-
{ id: 'cursor', name: 'Cursor', command: 'cursor', kind: '
|
|
14
|
-
{ id: 'code', name: 'VS Code', command: 'code', kind: '
|
|
15
|
-
{ id: 'windsurf', name: 'Windsurf', command: 'windsurf', kind: '
|
|
16
|
-
{ id: 'zed', name: 'Zed', command: 'zed', kind: '
|
|
17
|
-
{ id: '
|
|
18
|
-
{ id: '
|
|
19
|
-
{ id: '
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
export const OPEN_TOOL_CANDIDATES = [
|
|
13
|
+
{ id: 'cursor', name: 'Cursor', command: 'cursor', kind: 'editor', aliases: ['cursor'] },
|
|
14
|
+
{ id: 'code', name: 'VS Code', command: 'code', kind: 'editor', aliases: ['vscode', 'code'] },
|
|
15
|
+
{ id: 'windsurf', name: 'Windsurf', command: 'windsurf', kind: 'editor', aliases: ['windsurf'] },
|
|
16
|
+
{ id: 'zed', name: 'Zed', command: 'zed', kind: 'editor', aliases: ['zed'] },
|
|
17
|
+
{ id: 'idea', name: 'IntelliJ IDEA', command: 'idea', kind: 'editor', aliases: ['idea', 'intellij'] },
|
|
18
|
+
{ id: 'webstorm', name: 'WebStorm', command: 'webstorm', kind: 'editor', aliases: ['webstorm'] },
|
|
19
|
+
{ id: 'subl', name: 'Sublime Text', command: 'subl', kind: 'editor', aliases: ['subl', 'sublime'] },
|
|
20
|
+
{
|
|
21
|
+
id: 'codex-app',
|
|
22
|
+
name: 'Codex App',
|
|
23
|
+
command: 'codex-app',
|
|
24
|
+
kind: 'app',
|
|
25
|
+
aliases: ['codex', 'codex-app'],
|
|
26
|
+
bundleId: 'com.openai.codex',
|
|
27
|
+
},
|
|
28
|
+
{ id: 'cmux', name: 'Cmux Panel', command: 'cmux', kind: 'terminal', aliases: ['cmux', 'cmux-panel'] },
|
|
29
|
+
{ id: 'tmux', name: 'Tmux Session', command: 'tmux', kind: 'terminal', aliases: ['tmux', 'tmux-session'] },
|
|
25
30
|
];
|
|
31
|
+
const OTHER_COMMAND_ACTION = '__other_command__';
|
|
32
|
+
const NO_OPEN_ACTION = '__no_open__';
|
|
33
|
+
const OPEN_ACTION_PAGE_SIZE = 16;
|
|
34
|
+
const CMUX_TERMINAL_STARTUP_DELAY_MS = 350;
|
|
35
|
+
const yggtreeAccent = chalk.hex('#DEADED');
|
|
26
36
|
function truncateEnd(value, maxLen) {
|
|
27
37
|
if (maxLen <= 0)
|
|
28
38
|
return '';
|
|
@@ -52,6 +62,71 @@ function formatWorktreeChoiceLabel(type, branchName, displayPath, terminalColumn
|
|
|
52
62
|
const typeText = formatWorktreeType(type);
|
|
53
63
|
return `${typeText} ${chalk.yellow(branchText)} ${chalk.cyan(pathText)}`;
|
|
54
64
|
}
|
|
65
|
+
function openRail() {
|
|
66
|
+
return yggtreeAccent('│');
|
|
67
|
+
}
|
|
68
|
+
function formatOpenColumns(name, command, detail) {
|
|
69
|
+
const paddedName = name.padEnd(16);
|
|
70
|
+
const paddedCommand = command.padEnd(13);
|
|
71
|
+
const row = `${openRail()} ${chalk.bold(paddedName)} ${chalk.cyan(paddedCommand)}`;
|
|
72
|
+
return detail ? `${row} ${chalk.dim(detail)}` : row;
|
|
73
|
+
}
|
|
74
|
+
export function formatOpenToolChoice(tool) {
|
|
75
|
+
return formatOpenColumns(tool.name, tool.command, '');
|
|
76
|
+
}
|
|
77
|
+
function shellQuote(value) {
|
|
78
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
79
|
+
}
|
|
80
|
+
function formatTerminalSessionName(wtPath) {
|
|
81
|
+
const basename = path.basename(wtPath) || 'worktree';
|
|
82
|
+
const slug = basename.toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
83
|
+
return `yggtree-${slug || 'worktree'}`.slice(0, 80);
|
|
84
|
+
}
|
|
85
|
+
export function buildTmuxLaunchCommand(wtPath, env = process.env) {
|
|
86
|
+
const sessionName = formatTerminalSessionName(wtPath);
|
|
87
|
+
if (env.TMUX) {
|
|
88
|
+
return {
|
|
89
|
+
executable: 'tmux',
|
|
90
|
+
args: ['new-window', '-c', wtPath, '-n', sessionName],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
executable: 'tmux',
|
|
95
|
+
args: ['new-session', '-A', '-s', sessionName, '-c', wtPath],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function buildCmuxNewPaneCommand() {
|
|
99
|
+
return {
|
|
100
|
+
executable: 'cmux',
|
|
101
|
+
args: ['new-pane', '--type', 'terminal', '--direction', 'right', '--focus', 'true'],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function parseCmuxSurfaceRef(output) {
|
|
105
|
+
return output.match(/\bsurface:\S+/)?.[0];
|
|
106
|
+
}
|
|
107
|
+
function delay(ms) {
|
|
108
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
109
|
+
}
|
|
110
|
+
export function buildCmuxSendCommand(surfaceRef, wtPath) {
|
|
111
|
+
return {
|
|
112
|
+
executable: 'cmux',
|
|
113
|
+
args: ['send', '--surface', surfaceRef, `cd ${shellQuote(wtPath)}`],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function buildCmuxEnterCommand(surfaceRef) {
|
|
117
|
+
return {
|
|
118
|
+
executable: 'cmux',
|
|
119
|
+
args: ['send-key', '--surface', surfaceRef, 'Enter'],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function formatOpenSpecialChoice(value, allowShellAction) {
|
|
123
|
+
if (value === OTHER_COMMAND_ACTION) {
|
|
124
|
+
return formatOpenColumns('Other command', 'custom', 'run first, then stay in shell');
|
|
125
|
+
}
|
|
126
|
+
return allowShellAction
|
|
127
|
+
? formatOpenColumns('Nothing', 'skip', 'just enter the shell')
|
|
128
|
+
: formatOpenColumns('Do not open', 'skip', 'return after selection');
|
|
129
|
+
}
|
|
55
130
|
export async function commandExists(command) {
|
|
56
131
|
if (!command)
|
|
57
132
|
return false;
|
|
@@ -80,10 +155,29 @@ export async function commandExists(command) {
|
|
|
80
155
|
}
|
|
81
156
|
return false;
|
|
82
157
|
}
|
|
158
|
+
export async function macOSAppBundleExists(bundleId) {
|
|
159
|
+
if (process.platform !== 'darwin')
|
|
160
|
+
return false;
|
|
161
|
+
try {
|
|
162
|
+
const result = await execa('/usr/bin/mdfind', [`kMDItemCFBundleIdentifier == '${bundleId}'`]);
|
|
163
|
+
if (result.stdout.trim().length > 0)
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Fall back to common application roots when Spotlight is unavailable.
|
|
168
|
+
}
|
|
169
|
+
const commonAppPaths = [
|
|
170
|
+
'/Applications/Codex.app',
|
|
171
|
+
path.join(process.env.HOME || '', 'Applications', 'Codex.app'),
|
|
172
|
+
];
|
|
173
|
+
return commonAppPaths.some(appPath => fs.existsSync(appPath));
|
|
174
|
+
}
|
|
83
175
|
export async function detectInstalledOpenTools() {
|
|
84
176
|
const checks = await Promise.all(OPEN_TOOL_CANDIDATES.map(async (tool) => ({
|
|
85
177
|
tool,
|
|
86
|
-
exists:
|
|
178
|
+
exists: tool.bundleId
|
|
179
|
+
? await macOSAppBundleExists(tool.bundleId)
|
|
180
|
+
: await commandExists(tool.command),
|
|
87
181
|
})));
|
|
88
182
|
return checks
|
|
89
183
|
.filter(check => check.exists)
|
|
@@ -98,36 +192,39 @@ export function resolveOpenToolOption(input, installed) {
|
|
|
98
192
|
tool.command.toLowerCase() === normalized ||
|
|
99
193
|
tool.aliases?.some(alias => alias.toLowerCase() === normalized));
|
|
100
194
|
}
|
|
101
|
-
export function
|
|
102
|
-
|
|
195
|
+
export function resolveOpenToolCandidate(input) {
|
|
196
|
+
const normalized = input.trim().toLowerCase();
|
|
197
|
+
return OPEN_TOOL_CANDIDATES.find(tool => tool.id === normalized ||
|
|
198
|
+
tool.command.toLowerCase() === normalized ||
|
|
199
|
+
tool.aliases?.some(alias => alias.toLowerCase() === normalized));
|
|
103
200
|
}
|
|
104
|
-
|
|
105
|
-
|
|
201
|
+
async function resolveKnownOpenToolOption(input) {
|
|
202
|
+
const candidate = resolveOpenToolCandidate(input);
|
|
203
|
+
if (!candidate)
|
|
204
|
+
return undefined;
|
|
205
|
+
const exists = candidate.bundleId
|
|
206
|
+
? await macOSAppBundleExists(candidate.bundleId)
|
|
207
|
+
: await commandExists(candidate.command);
|
|
208
|
+
return exists ? candidate : undefined;
|
|
209
|
+
}
|
|
210
|
+
export async function resolveOpenToolAction(input, installed) {
|
|
211
|
+
let chosenTool = resolveOpenToolOption(input, installed) || await resolveKnownOpenToolOption(input);
|
|
212
|
+
if (!chosenTool && await commandExists(input)) {
|
|
213
|
+
chosenTool = {
|
|
214
|
+
id: 'custom',
|
|
215
|
+
name: input,
|
|
216
|
+
command: input,
|
|
217
|
+
kind: 'editor',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return chosenTool ? { type: 'tool', tool: chosenTool } : undefined;
|
|
106
221
|
}
|
|
107
222
|
export async function promptOpenToolSelection(installedTools, message = 'Select tool to open:') {
|
|
108
|
-
const
|
|
109
|
-
.filter(tool => tool.kind === 'ide')
|
|
223
|
+
const choices = installedTools
|
|
110
224
|
.map(tool => ({
|
|
111
|
-
name:
|
|
225
|
+
name: formatOpenToolChoice(tool),
|
|
112
226
|
value: tool,
|
|
113
227
|
}));
|
|
114
|
-
const agentChoices = installedTools
|
|
115
|
-
.filter(tool => tool.kind === 'agent')
|
|
116
|
-
.map(tool => ({
|
|
117
|
-
name: `${tool.name} ${chalk.dim(`(${tool.command})`)}`,
|
|
118
|
-
value: tool,
|
|
119
|
-
}));
|
|
120
|
-
const choices = [];
|
|
121
|
-
if (ideChoices.length > 0) {
|
|
122
|
-
choices.push(new inquirer.Separator(chalk.dim('── IDEs ──')));
|
|
123
|
-
choices.push(...ideChoices);
|
|
124
|
-
}
|
|
125
|
-
if (agentChoices.length > 0) {
|
|
126
|
-
if (choices.length > 0)
|
|
127
|
-
choices.push(new inquirer.Separator(" "));
|
|
128
|
-
choices.push(new inquirer.Separator(chalk.dim('── Agent CLIs ──')));
|
|
129
|
-
choices.push(...agentChoices);
|
|
130
|
-
}
|
|
131
228
|
const { selectedTool } = await inquirer.prompt([
|
|
132
229
|
{
|
|
133
230
|
type: 'list',
|
|
@@ -135,21 +232,136 @@ export async function promptOpenToolSelection(installedTools, message = 'Select
|
|
|
135
232
|
message,
|
|
136
233
|
choices,
|
|
137
234
|
loop: false,
|
|
138
|
-
pageSize:
|
|
235
|
+
pageSize: OPEN_ACTION_PAGE_SIZE,
|
|
139
236
|
},
|
|
140
237
|
]);
|
|
141
238
|
return selectedTool;
|
|
142
239
|
}
|
|
240
|
+
export function buildOpenActionsFromSelection(selectedValue, installedTools, otherCommand) {
|
|
241
|
+
if (selectedValue === NO_OPEN_ACTION)
|
|
242
|
+
return [];
|
|
243
|
+
if (selectedValue.startsWith('tool:')) {
|
|
244
|
+
const tool = installedTools.find(candidate => `tool:${candidate.id}` === selectedValue);
|
|
245
|
+
return tool ? [{ type: 'tool', tool }] : [];
|
|
246
|
+
}
|
|
247
|
+
if (selectedValue === OTHER_COMMAND_ACTION && otherCommand?.trim()) {
|
|
248
|
+
return [{ type: 'other-command', command: otherCommand.trim() }];
|
|
249
|
+
}
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
export function buildOpenActionChoices(installedTools, allowShellAction) {
|
|
253
|
+
const choices = installedTools.map(tool => ({
|
|
254
|
+
name: formatOpenToolChoice(tool),
|
|
255
|
+
value: `tool:${tool.id}`,
|
|
256
|
+
}));
|
|
257
|
+
if (allowShellAction) {
|
|
258
|
+
choices.push({
|
|
259
|
+
name: formatOpenSpecialChoice(OTHER_COMMAND_ACTION, allowShellAction),
|
|
260
|
+
value: OTHER_COMMAND_ACTION,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
choices.push({
|
|
264
|
+
name: formatOpenSpecialChoice(NO_OPEN_ACTION, allowShellAction),
|
|
265
|
+
value: NO_OPEN_ACTION,
|
|
266
|
+
});
|
|
267
|
+
return choices;
|
|
268
|
+
}
|
|
269
|
+
export async function promptOpenActions(installedTools, options = {}) {
|
|
270
|
+
const allowShellAction = options.allowShellAction ?? true;
|
|
271
|
+
const choices = buildOpenActionChoices(installedTools, allowShellAction);
|
|
272
|
+
const { selectedValue } = await inquirer.prompt([
|
|
273
|
+
{
|
|
274
|
+
type: 'list',
|
|
275
|
+
name: 'selectedValue',
|
|
276
|
+
message: options.message || 'Open anything before entering the shell?',
|
|
277
|
+
choices,
|
|
278
|
+
loop: false,
|
|
279
|
+
pageSize: OPEN_ACTION_PAGE_SIZE,
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
let otherCommand;
|
|
283
|
+
if (selectedValue === OTHER_COMMAND_ACTION) {
|
|
284
|
+
const answer = await inquirer.prompt([
|
|
285
|
+
{
|
|
286
|
+
type: 'input',
|
|
287
|
+
name: 'otherCommand',
|
|
288
|
+
message: 'Command to run in the worktree shell:',
|
|
289
|
+
validate: (input) => input.trim().length > 0 || 'Command is required',
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
otherCommand = answer.otherCommand;
|
|
293
|
+
}
|
|
294
|
+
return buildOpenActionsFromSelection(selectedValue, installedTools, otherCommand);
|
|
295
|
+
}
|
|
143
296
|
export async function launchOpenTool(tool, wtPath) {
|
|
144
|
-
if (
|
|
145
|
-
|
|
297
|
+
if (tool.id === 'cmux') {
|
|
298
|
+
const newPaneCommand = buildCmuxNewPaneCommand();
|
|
299
|
+
const newPaneResult = await execa(newPaneCommand.executable, newPaneCommand.args, {
|
|
300
|
+
cwd: wtPath,
|
|
301
|
+
});
|
|
302
|
+
const surfaceRef = parseCmuxSurfaceRef(newPaneResult.stdout);
|
|
303
|
+
if (!surfaceRef) {
|
|
304
|
+
throw new Error(`Cmux did not return a terminal surface handle: ${newPaneResult.stdout || '<empty output>'}`);
|
|
305
|
+
}
|
|
306
|
+
await delay(CMUX_TERMINAL_STARTUP_DELAY_MS);
|
|
307
|
+
const sendCommand = buildCmuxSendCommand(surfaceRef, wtPath);
|
|
308
|
+
await execa(sendCommand.executable, sendCommand.args, {
|
|
309
|
+
cwd: wtPath,
|
|
310
|
+
stdio: 'ignore',
|
|
311
|
+
});
|
|
312
|
+
const enterCommand = buildCmuxEnterCommand(surfaceRef);
|
|
313
|
+
await execa(enterCommand.executable, enterCommand.args, {
|
|
314
|
+
cwd: wtPath,
|
|
315
|
+
stdio: 'ignore',
|
|
316
|
+
});
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (tool.id === 'tmux') {
|
|
320
|
+
const { executable, args } = buildTmuxLaunchCommand(wtPath);
|
|
321
|
+
await execa(executable, args, {
|
|
322
|
+
cwd: wtPath,
|
|
323
|
+
stdio: 'inherit',
|
|
324
|
+
});
|
|
146
325
|
return;
|
|
147
326
|
}
|
|
148
|
-
|
|
327
|
+
const { executable, args } = buildOpenToolLaunchCommand(tool, wtPath);
|
|
328
|
+
await execa(executable, args, {
|
|
149
329
|
cwd: wtPath,
|
|
150
330
|
stdio: 'ignore',
|
|
151
331
|
});
|
|
152
332
|
}
|
|
333
|
+
export function buildOpenToolLaunchCommand(tool, wtPath) {
|
|
334
|
+
if (tool.bundleId) {
|
|
335
|
+
return {
|
|
336
|
+
executable: '/usr/bin/open',
|
|
337
|
+
args: ['-b', tool.bundleId, wtPath],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
executable: tool.command,
|
|
342
|
+
args: [wtPath],
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
export async function runOpenActions(actions, wtPath, options = {}) {
|
|
346
|
+
let openedTerminal = false;
|
|
347
|
+
for (const action of actions) {
|
|
348
|
+
if (action.type === 'tool' && action.tool) {
|
|
349
|
+
log.info(`Opening ${ui.path(wtPath)} in ${chalk.cyan(action.tool.name)}...`);
|
|
350
|
+
await launchOpenTool(action.tool, wtPath);
|
|
351
|
+
if (action.tool.kind === 'terminal') {
|
|
352
|
+
openedTerminal = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const shellAction = actions.find(action => action.type === 'other-command');
|
|
357
|
+
if (shellAction?.command) {
|
|
358
|
+
await enterCommand(wtPath, { exec: shellAction.command });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (options.enter !== false && !openedTerminal) {
|
|
362
|
+
await enterCommand(wtPath);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
153
365
|
export async function openCommand(wtName, options = {}) {
|
|
154
366
|
try {
|
|
155
367
|
await getRepoRoot();
|
|
@@ -208,36 +420,33 @@ export async function openCommand(wtName, options = {}) {
|
|
|
208
420
|
return;
|
|
209
421
|
}
|
|
210
422
|
const installedTools = await detectInstalledOpenTools();
|
|
211
|
-
let chosenTool;
|
|
212
423
|
const requestedTool = options.tool;
|
|
424
|
+
const shouldEnter = options.enter === true;
|
|
213
425
|
if (requestedTool) {
|
|
214
|
-
|
|
215
|
-
if (!
|
|
216
|
-
chosenTool = {
|
|
217
|
-
id: 'custom',
|
|
218
|
-
name: requestedTool,
|
|
219
|
-
command: requestedTool,
|
|
220
|
-
kind: 'ide',
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
if (!chosenTool) {
|
|
426
|
+
const toolAction = await resolveOpenToolAction(requestedTool, installedTools);
|
|
427
|
+
if (!toolAction) {
|
|
224
428
|
const available = installedTools.map(tool => tool.command).join(', ') || 'none detected';
|
|
225
429
|
log.error(`Tool "${requestedTool}" not found.`);
|
|
226
430
|
log.dim(`Detected tool commands: ${available}`);
|
|
227
431
|
return;
|
|
228
432
|
}
|
|
433
|
+
await runOpenActions([toolAction], targetWt.path, { enter: shouldEnter });
|
|
434
|
+
log.success('Worktree opened.');
|
|
435
|
+
return;
|
|
229
436
|
}
|
|
230
437
|
else {
|
|
231
|
-
if (installedTools.length === 0) {
|
|
232
|
-
log.error('No supported
|
|
233
|
-
log.dim('Try: yggtree
|
|
438
|
+
if (installedTools.length === 0 && !shouldEnter) {
|
|
439
|
+
log.error('No supported editor command or app was detected.');
|
|
440
|
+
log.dim('Try: yggtree open --tool <command> (e.g. --tool cursor or --tool codex-app), or yggtree open --enter');
|
|
234
441
|
return;
|
|
235
442
|
}
|
|
236
|
-
|
|
443
|
+
const actions = await promptOpenActions(installedTools, {
|
|
444
|
+
allowShellAction: shouldEnter,
|
|
445
|
+
message: shouldEnter ? 'Open anything before entering the shell?' : 'Open anything?',
|
|
446
|
+
});
|
|
447
|
+
await runOpenActions(actions, targetWt.path, { enter: shouldEnter });
|
|
237
448
|
}
|
|
238
|
-
log.
|
|
239
|
-
await launchOpenTool(chosenTool, targetWt.path);
|
|
240
|
-
log.success(isAgentTool(chosenTool) ? 'Agent launched.' : 'IDE launched.');
|
|
449
|
+
log.success('Worktree opened.');
|
|
241
450
|
}
|
|
242
451
|
catch (error) {
|
|
243
452
|
log.error(error.message);
|