wave-code 0.7.2 → 0.8.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.
Files changed (110) hide show
  1. package/dist/cli.d.ts +2 -4
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +24 -52
  4. package/dist/components/App.d.ts +3 -4
  5. package/dist/components/App.d.ts.map +1 -1
  6. package/dist/components/App.js +49 -6
  7. package/dist/components/BangDisplay.d.ts +9 -0
  8. package/dist/components/BangDisplay.d.ts.map +1 -0
  9. package/dist/components/{CommandOutputDisplay.js → BangDisplay.js} +1 -1
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +3 -2
  12. package/dist/components/CommandSelector.d.ts.map +1 -1
  13. package/dist/components/CommandSelector.js +7 -28
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  15. package/dist/components/ConfirmationSelector.js +116 -11
  16. package/dist/components/HelpView.d.ts +2 -0
  17. package/dist/components/HelpView.d.ts.map +1 -1
  18. package/dist/components/HelpView.js +49 -3
  19. package/dist/components/InputBox.d.ts.map +1 -1
  20. package/dist/components/InputBox.js +10 -4
  21. package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
  22. package/dist/components/MarketplaceAddForm.js +13 -6
  23. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  24. package/dist/components/MarketplaceDetail.js +8 -3
  25. package/dist/components/MessageBlockItem.js +2 -2
  26. package/dist/components/MessageList.d.ts +4 -1
  27. package/dist/components/MessageList.d.ts.map +1 -1
  28. package/dist/components/MessageList.js +15 -8
  29. package/dist/components/PluginDetail.d.ts.map +1 -1
  30. package/dist/components/PluginDetail.js +14 -3
  31. package/dist/components/PluginManagerShell.d.ts.map +1 -1
  32. package/dist/components/PluginManagerShell.js +3 -3
  33. package/dist/components/PluginManagerTypes.d.ts +2 -0
  34. package/dist/components/PluginManagerTypes.d.ts.map +1 -1
  35. package/dist/components/SessionSelector.d.ts.map +1 -1
  36. package/dist/components/SessionSelector.js +5 -5
  37. package/dist/components/StatusCommand.d.ts +6 -0
  38. package/dist/components/StatusCommand.d.ts.map +1 -0
  39. package/dist/components/StatusCommand.js +28 -0
  40. package/dist/components/WorktreeExitPrompt.d.ts +13 -0
  41. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -0
  42. package/dist/components/WorktreeExitPrompt.js +26 -0
  43. package/dist/constants/commands.d.ts +3 -0
  44. package/dist/constants/commands.d.ts.map +1 -0
  45. package/dist/constants/commands.js +38 -0
  46. package/dist/contexts/useChat.d.ts +9 -5
  47. package/dist/contexts/useChat.d.ts.map +1 -1
  48. package/dist/contexts/useChat.js +38 -8
  49. package/dist/contracts/status.d.ts +8 -0
  50. package/dist/contracts/status.d.ts.map +1 -0
  51. package/dist/contracts/status.js +1 -0
  52. package/dist/hooks/useInputManager.d.ts +2 -0
  53. package/dist/hooks/useInputManager.d.ts.map +1 -1
  54. package/dist/hooks/useInputManager.js +12 -0
  55. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  56. package/dist/hooks/usePluginManager.js +41 -13
  57. package/dist/hooks/useTasks.js +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +53 -4
  60. package/dist/managers/InputManager.d.ts +6 -0
  61. package/dist/managers/InputManager.d.ts.map +1 -1
  62. package/dist/managers/InputManager.js +32 -13
  63. package/dist/print-cli.d.ts +2 -4
  64. package/dist/print-cli.d.ts.map +1 -1
  65. package/dist/print-cli.js +31 -2
  66. package/dist/session-selector-cli.d.ts +3 -1
  67. package/dist/session-selector-cli.d.ts.map +1 -1
  68. package/dist/session-selector-cli.js +2 -2
  69. package/dist/types.d.ts +11 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +1 -0
  72. package/dist/utils/highlightUtils.d.ts.map +1 -1
  73. package/dist/utils/highlightUtils.js +66 -42
  74. package/dist/utils/worktree.d.ts +23 -0
  75. package/dist/utils/worktree.d.ts.map +1 -0
  76. package/dist/utils/worktree.js +135 -0
  77. package/package.json +2 -2
  78. package/src/cli.tsx +36 -59
  79. package/src/components/App.tsx +99 -11
  80. package/src/components/{CommandOutputDisplay.tsx → BangDisplay.tsx} +4 -4
  81. package/src/components/ChatInterface.tsx +8 -0
  82. package/src/components/CommandSelector.tsx +7 -29
  83. package/src/components/ConfirmationSelector.tsx +131 -12
  84. package/src/components/HelpView.tsx +129 -14
  85. package/src/components/InputBox.tsx +14 -2
  86. package/src/components/MarketplaceAddForm.tsx +21 -8
  87. package/src/components/MarketplaceDetail.tsx +19 -4
  88. package/src/components/MessageBlockItem.tsx +3 -3
  89. package/src/components/MessageList.tsx +47 -23
  90. package/src/components/PluginDetail.tsx +30 -6
  91. package/src/components/PluginManagerShell.tsx +24 -6
  92. package/src/components/PluginManagerTypes.ts +2 -0
  93. package/src/components/SessionSelector.tsx +33 -16
  94. package/src/components/StatusCommand.tsx +94 -0
  95. package/src/components/WorktreeExitPrompt.tsx +86 -0
  96. package/src/constants/commands.ts +41 -0
  97. package/src/contexts/useChat.tsx +57 -13
  98. package/src/contracts/status.ts +7 -0
  99. package/src/hooks/useInputManager.ts +12 -0
  100. package/src/hooks/usePluginManager.ts +47 -13
  101. package/src/hooks/useTasks.ts +2 -2
  102. package/src/index.ts +71 -12
  103. package/src/managers/InputManager.ts +37 -15
  104. package/src/print-cli.ts +48 -5
  105. package/src/session-selector-cli.tsx +6 -2
  106. package/src/types.ts +11 -0
  107. package/src/utils/highlightUtils.ts +66 -42
  108. package/src/utils/worktree.ts +164 -0
  109. package/dist/components/CommandOutputDisplay.d.ts +0 -9
  110. package/dist/components/CommandOutputDisplay.d.ts.map +0 -1
package/src/index.ts CHANGED
@@ -1,11 +1,21 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
3
  import { startCli } from "./cli.js";
4
- import { Scope } from "wave-agent-sdk";
4
+ import { Scope, generateRandomName } from "wave-agent-sdk";
5
+ import { createWorktree, type WorktreeSession } from "./utils/worktree.js";
6
+ import path from "path";
7
+ import { readFileSync } from "fs";
8
+ import { fileURLToPath } from "url";
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const packageJsonPath = path.resolve(__dirname, "../package.json");
12
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
13
+ const version = packageJson.version;
5
14
 
6
15
  // Export main function for external use
7
16
  export async function main() {
8
17
  try {
18
+ const originalCwd = process.cwd();
9
19
  const argv = await yargs(hideBin(process.argv))
10
20
  .option("restore", {
11
21
  alias: "r",
@@ -20,6 +30,12 @@ export async function main() {
20
30
  type: "boolean",
21
31
  global: false,
22
32
  })
33
+ .option("worktree", {
34
+ alias: "w",
35
+ description: "Start session in a git worktree (optional name)",
36
+ type: "string",
37
+ global: false,
38
+ })
23
39
  .option("print", {
24
40
  alias: "p",
25
41
  description: "Print response without interactive mode",
@@ -49,6 +65,11 @@ export async function main() {
49
65
  type: "string",
50
66
  global: false,
51
67
  })
68
+ .option("model", {
69
+ description: "Specify the AI model to use",
70
+ type: "string",
71
+ global: false,
72
+ })
52
73
  .command(
53
74
  "plugin",
54
75
  "Manage plugins and marketplaces",
@@ -231,6 +252,30 @@ export async function main() {
231
252
 
232
253
  const tools = parseTools(argv.tools as string | undefined);
233
254
 
255
+ // Resolve plugin directories to absolute paths before any worktree logic
256
+ const pluginDirs = (argv.pluginDir as string[] | undefined)?.map((dir) =>
257
+ path.resolve(originalCwd, dir),
258
+ );
259
+
260
+ let worktreeSession: WorktreeSession | undefined;
261
+ if (
262
+ argv.worktree !== undefined ||
263
+ process.argv.includes("-w") ||
264
+ process.argv.includes("--worktree")
265
+ ) {
266
+ let name = argv.worktree as string | undefined;
267
+ if (!name || name === "") {
268
+ name = generateRandomName();
269
+ }
270
+ worktreeSession = createWorktree(name, originalCwd);
271
+ }
272
+
273
+ const workdir = worktreeSession?.path || originalCwd;
274
+
275
+ if (worktreeSession) {
276
+ process.chdir(workdir);
277
+ }
278
+
234
279
  // Handle restore session command
235
280
  if (
236
281
  argv.restore === "" ||
@@ -241,7 +286,7 @@ export async function main() {
241
286
  const { startSessionSelectorCli } = await import(
242
287
  "./session-selector-cli.js"
243
288
  );
244
- const selectedSessionId = await startSessionSelectorCli();
289
+ const selectedSessionId = await startSessionSelectorCli({ workdir });
245
290
  if (!selectedSessionId) {
246
291
  return;
247
292
  }
@@ -249,8 +294,12 @@ export async function main() {
249
294
  return startCli({
250
295
  restoreSessionId: selectedSessionId,
251
296
  bypassPermissions: argv.dangerouslySkipPermissions,
252
- pluginDirs: argv.pluginDir as string[],
297
+ pluginDirs,
253
298
  tools,
299
+ worktreeSession,
300
+ workdir,
301
+ version,
302
+ model: argv.model as string | undefined,
254
303
  });
255
304
  }
256
305
 
@@ -258,22 +307,32 @@ export async function main() {
258
307
  if (argv.print !== undefined) {
259
308
  const { startPrintCli } = await import("./print-cli.js");
260
309
  return startPrintCli({
261
- restoreSessionId: argv.restore,
262
- continueLastSession: argv.continue,
310
+ restoreSessionId: argv.restore as string | undefined,
311
+ continueLastSession: argv.continue as boolean | undefined,
263
312
  message: argv.print,
264
- showStats: argv.showStats,
265
- bypassPermissions: argv.dangerouslySkipPermissions,
266
- pluginDirs: argv.pluginDir as string[],
313
+ showStats: argv.showStats as boolean | undefined,
314
+ bypassPermissions: argv.dangerouslySkipPermissions as
315
+ | boolean
316
+ | undefined,
317
+ pluginDirs,
267
318
  tools,
319
+ worktreeSession,
320
+ workdir,
321
+ version,
322
+ model: argv.model as string | undefined,
268
323
  });
269
324
  }
270
325
 
271
326
  await startCli({
272
- restoreSessionId: argv.restore,
273
- continueLastSession: argv.continue,
274
- bypassPermissions: argv.dangerouslySkipPermissions,
275
- pluginDirs: argv.pluginDir as string[],
327
+ restoreSessionId: argv.restore as string | undefined,
328
+ continueLastSession: argv.continue as boolean | undefined,
329
+ bypassPermissions: argv.dangerouslySkipPermissions as boolean | undefined,
330
+ pluginDirs,
276
331
  tools,
332
+ worktreeSession,
333
+ workdir,
334
+ version,
335
+ model: argv.model as string | undefined,
277
336
  });
278
337
  } catch (error) {
279
338
  console.error("Failed to start WAVE Code:", error);
@@ -33,6 +33,7 @@ export interface InputManagerCallbacks {
33
33
  onMcpManagerStateChange?: (show: boolean) => void;
34
34
  onRewindManagerStateChange?: (show: boolean) => void;
35
35
  onHelpStateChange?: (show: boolean) => void;
36
+ onStatusCommandStateChange?: (show: boolean) => void;
36
37
  onImagesStateChange?: (images: AttachedImage[]) => void;
37
38
  onSendMessage?: (
38
39
  content: string,
@@ -40,6 +41,7 @@ export interface InputManagerCallbacks {
40
41
  ) => void | Promise<void>;
41
42
  onHasSlashCommand?: (commandId: string) => boolean;
42
43
  onAbortMessage?: () => void;
44
+ onClearMessages?: () => void;
43
45
  onBackgroundCurrentTask?: () => void;
44
46
  onResetHistoryNavigation?: () => void;
45
47
  onPermissionModeChange?: (mode: PermissionMode) => void;
@@ -86,6 +88,7 @@ export class InputManager {
86
88
  private showMcpManager: boolean = false;
87
89
  private showRewindManager: boolean = false;
88
90
  private showHelp: boolean = false;
91
+ private showStatusCommand: boolean = false;
89
92
 
90
93
  // Permission mode state
91
94
  private permissionMode: PermissionMode = "default";
@@ -342,7 +345,10 @@ export class InputManager {
342
345
 
343
346
  // If not an agent command or execution failed, check local commands
344
347
  if (!commandExecuted) {
345
- if (command === "tasks") {
348
+ if (command === "clear") {
349
+ this.callbacks.onClearMessages?.();
350
+ commandExecuted = true;
351
+ } else if (command === "tasks") {
346
352
  this.setShowBackgroundTaskManager(true);
347
353
  commandExecuted = true;
348
354
  } else if (command === "mcp") {
@@ -354,6 +360,9 @@ export class InputManager {
354
360
  } else if (command === "help") {
355
361
  this.setShowHelp(true);
356
362
  commandExecuted = true;
363
+ } else if (command === "status") {
364
+ this.setShowStatusCommand(true);
365
+ commandExecuted = true;
357
366
  }
358
367
  }
359
368
  })();
@@ -667,11 +676,33 @@ export class InputManager {
667
676
  this.callbacks.onHelpStateChange?.(show);
668
677
  }
669
678
 
679
+ getShowStatusCommand(): boolean {
680
+ return this.showStatusCommand;
681
+ }
682
+
683
+ setShowStatusCommand(show: boolean): void {
684
+ this.showStatusCommand = show;
685
+ this.callbacks.onStatusCommandStateChange?.(show);
686
+ }
687
+
670
688
  // Permission mode methods
671
689
  getPermissionMode(): PermissionMode {
672
690
  return this.permissionMode;
673
691
  }
674
692
 
693
+ isAnySelectorOrManagerActive(): boolean {
694
+ return (
695
+ this.showFileSelector ||
696
+ this.showCommandSelector ||
697
+ this.showHistorySearch ||
698
+ this.showBackgroundTaskManager ||
699
+ this.showMcpManager ||
700
+ this.showRewindManager ||
701
+ this.showHelp ||
702
+ this.showStatusCommand
703
+ );
704
+ }
705
+
675
706
  setPermissionMode(mode: PermissionMode): void {
676
707
  this.permissionMode = mode;
677
708
  }
@@ -920,9 +951,7 @@ export class InputManager {
920
951
  if (
921
952
  key.escape &&
922
953
  (isLoading || isCommandRunning) &&
923
- !this.showBackgroundTaskManager &&
924
- !this.showMcpManager &&
925
- !this.showRewindManager
954
+ !this.isAnySelectorOrManagerActive()
926
955
  ) {
927
956
  // Unified interrupt for AI message generation and command execution
928
957
  this.callbacks.onAbortMessage?.();
@@ -937,22 +966,15 @@ export class InputManager {
937
966
  }
938
967
 
939
968
  // Check if any selector is active
940
- if (
941
- this.showFileSelector ||
942
- this.showCommandSelector ||
943
- this.showHistorySearch ||
944
- this.showBackgroundTaskManager ||
945
- this.showMcpManager ||
946
- this.showRewindManager ||
947
- this.showHelp
948
- ) {
969
+ if (this.isAnySelectorOrManagerActive()) {
949
970
  if (
950
971
  this.showBackgroundTaskManager ||
951
972
  this.showMcpManager ||
952
973
  this.showRewindManager ||
953
- this.showHelp
974
+ this.showHelp ||
975
+ this.showStatusCommand
954
976
  ) {
955
- // Task manager, MCP manager, Rewind and Help don't need to handle input, handled by component itself
977
+ // Task manager, MCP manager, Rewind, Help and Status don't need to handle input, handled by component itself
956
978
  // Return true to indicate we've "handled" it (by ignoring it) so it doesn't leak to normal input
957
979
  return true;
958
980
  }
package/src/print-cli.ts CHANGED
@@ -1,14 +1,19 @@
1
- import { Agent, AgentCallbacks } from "wave-agent-sdk";
1
+ import {
2
+ Agent,
3
+ AgentCallbacks,
4
+ hasUncommittedChanges,
5
+ hasNewCommits,
6
+ getDefaultRemoteBranch,
7
+ } from "wave-agent-sdk";
2
8
  import { displayUsageSummary } from "./utils/usageSummary.js";
9
+ import { removeWorktree } from "./utils/worktree.js";
10
+ import { BaseAppProps } from "./types.js";
3
11
 
4
- export interface PrintCliOptions {
12
+ export interface PrintCliOptions extends BaseAppProps {
5
13
  restoreSessionId?: string;
6
14
  continueLastSession?: boolean;
7
15
  message?: string;
8
16
  showStats?: boolean;
9
- bypassPermissions?: boolean;
10
- pluginDirs?: string[];
11
- tools?: string[];
12
17
  }
13
18
 
14
19
  function displayTimingInfo(startTime: Date, showStats: boolean): void {
@@ -35,6 +40,9 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
35
40
  bypassPermissions,
36
41
  pluginDirs,
37
42
  tools,
43
+ worktreeSession,
44
+ workdir,
45
+ model,
38
46
  } = options;
39
47
 
40
48
  if (
@@ -139,6 +147,8 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
139
147
  permissionMode: bypassPermissions ? "bypassPermissions" : undefined,
140
148
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
141
149
  tools,
150
+ workdir,
151
+ model,
142
152
  // 保持流式模式以获得更好的命令行用户体验
143
153
  });
144
154
 
@@ -163,6 +173,23 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
163
173
 
164
174
  // Destroy agent and exit after sendMessage completes
165
175
  await agent.destroy();
176
+
177
+ // Handle worktree cleanup for print mode
178
+ if (worktreeSession) {
179
+ const cwd = workdir || worktreeSession.path;
180
+ const baseBranch = getDefaultRemoteBranch(cwd);
181
+ const hasChanges = hasUncommittedChanges(cwd);
182
+ const hasCommits = hasNewCommits(cwd, baseBranch);
183
+
184
+ if (!hasChanges && !hasCommits) {
185
+ removeWorktree(worktreeSession);
186
+ } else {
187
+ process.stdout.write(
188
+ `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
189
+ );
190
+ }
191
+ }
192
+
166
193
  process.exit(0);
167
194
  } catch (error) {
168
195
  console.error("Failed to send message:", error);
@@ -182,6 +209,22 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
182
209
  displayTimingInfo(startTime, showStats);
183
210
 
184
211
  await agent.destroy();
212
+
213
+ // Handle worktree cleanup for print mode even on error
214
+ if (worktreeSession) {
215
+ const cwd = workdir || worktreeSession.path;
216
+ const baseBranch = getDefaultRemoteBranch(cwd);
217
+ const hasChanges = hasUncommittedChanges(cwd);
218
+ const hasCommits = hasNewCommits(cwd, baseBranch);
219
+
220
+ if (!hasChanges && !hasCommits) {
221
+ removeWorktree(worktreeSession);
222
+ } else {
223
+ process.stdout.write(
224
+ `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
225
+ );
226
+ }
227
+ }
185
228
  }
186
229
  process.exit(1);
187
230
  }
@@ -3,8 +3,12 @@ import { render, Box } from "ink";
3
3
  import { listSessions, truncateContent } from "wave-agent-sdk";
4
4
  import { SessionSelector } from "./components/SessionSelector.js";
5
5
 
6
- export async function startSessionSelectorCli(): Promise<string | null> {
7
- const currentWorkdir = process.cwd();
6
+ export async function startSessionSelectorCli({
7
+ workdir,
8
+ }: {
9
+ workdir?: string;
10
+ } = {}): Promise<string | null> {
11
+ const currentWorkdir = workdir || process.cwd();
8
12
  const sessions = await listSessions(currentWorkdir);
9
13
 
10
14
  if (sessions.length === 0) {
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { WorktreeSession } from "./utils/worktree.js";
2
+
3
+ export interface BaseAppProps {
4
+ bypassPermissions?: boolean;
5
+ pluginDirs?: string[];
6
+ tools?: string[];
7
+ worktreeSession?: WorktreeSession;
8
+ workdir?: string;
9
+ version?: string;
10
+ model?: string;
11
+ }
@@ -1,41 +1,41 @@
1
- import hljs from 'highlight.js';
2
- import { parse, Node, HTMLElement, TextNode } from 'node-html-parser';
3
- import chalk from 'chalk';
1
+ import hljs from "highlight.js";
2
+ import { parse, Node, HTMLElement, TextNode } from "node-html-parser";
3
+ import chalk from "chalk";
4
4
 
5
5
  const theme: Record<string, (text: string) => string> = {
6
- 'hljs-keyword': chalk.blue,
7
- 'hljs-built_in': chalk.cyan,
8
- 'hljs-type': chalk.cyan,
9
- 'hljs-literal': chalk.magenta,
10
- 'hljs-number': chalk.magenta,
11
- 'hljs-operator': chalk.white,
12
- 'hljs-punctuation': chalk.white,
13
- 'hljs-property': chalk.yellow,
14
- 'hljs-attr': chalk.yellow,
15
- 'hljs-variable': chalk.white,
16
- 'hljs-template-variable': chalk.white,
17
- 'hljs-string': chalk.green,
18
- 'hljs-char': chalk.green,
19
- 'hljs-comment': chalk.gray,
20
- 'hljs-doctag': chalk.gray,
21
- 'hljs-function': chalk.yellow,
22
- 'hljs-title': chalk.yellow,
23
- 'hljs-params': chalk.white,
24
- 'hljs-tag': chalk.blue,
25
- 'hljs-name': chalk.blue,
26
- 'hljs-selector-tag': chalk.blue,
27
- 'hljs-selector-id': chalk.blue,
28
- 'hljs-selector-class': chalk.blue,
29
- 'hljs-selector-attr': chalk.blue,
30
- 'hljs-selector-pseudo': chalk.blue,
31
- 'hljs-subst': chalk.white,
32
- 'hljs-section': chalk.blue.bold,
33
- 'hljs-bullet': chalk.magenta,
34
- 'hljs-emphasis': chalk.italic,
35
- 'hljs-strong': chalk.bold,
36
- 'hljs-addition': chalk.green,
37
- 'hljs-deletion': chalk.red,
38
- 'hljs-link': chalk.blue.underline,
6
+ "hljs-keyword": chalk.blue,
7
+ "hljs-built_in": chalk.cyan,
8
+ "hljs-type": chalk.cyan,
9
+ "hljs-literal": chalk.magenta,
10
+ "hljs-number": chalk.magenta,
11
+ "hljs-operator": chalk.white,
12
+ "hljs-punctuation": chalk.white,
13
+ "hljs-property": chalk.yellow,
14
+ "hljs-attr": chalk.yellow,
15
+ "hljs-variable": chalk.white,
16
+ "hljs-template-variable": chalk.white,
17
+ "hljs-string": chalk.green,
18
+ "hljs-char": chalk.green,
19
+ "hljs-comment": chalk.gray,
20
+ "hljs-doctag": chalk.gray,
21
+ "hljs-function": chalk.yellow,
22
+ "hljs-title": chalk.yellow,
23
+ "hljs-params": chalk.white,
24
+ "hljs-tag": chalk.blue,
25
+ "hljs-name": chalk.blue,
26
+ "hljs-selector-tag": chalk.blue,
27
+ "hljs-selector-id": chalk.blue,
28
+ "hljs-selector-class": chalk.blue,
29
+ "hljs-selector-attr": chalk.blue,
30
+ "hljs-selector-pseudo": chalk.blue,
31
+ "hljs-subst": chalk.white,
32
+ "hljs-section": chalk.blue.bold,
33
+ "hljs-bullet": chalk.magenta,
34
+ "hljs-emphasis": chalk.italic,
35
+ "hljs-strong": chalk.bold,
36
+ "hljs-addition": chalk.green,
37
+ "hljs-deletion": chalk.red,
38
+ "hljs-link": chalk.blue.underline,
39
39
  };
40
40
 
41
41
  function nodeToAnsi(node: Node): string {
@@ -44,8 +44,8 @@ function nodeToAnsi(node: Node): string {
44
44
  }
45
45
 
46
46
  if (node instanceof HTMLElement) {
47
- const content = node.childNodes.map(nodeToAnsi).join('');
48
- const classes = node.getAttribute('class')?.split(/\s+/) || [];
47
+ const content = node.childNodes.map(nodeToAnsi).join("");
48
+ const classes = node.getAttribute("class")?.split(/\s+/) || [];
49
49
 
50
50
  for (const className of classes) {
51
51
  if (theme[className]) {
@@ -56,20 +56,44 @@ function nodeToAnsi(node: Node): string {
56
56
  return content;
57
57
  }
58
58
 
59
- return '';
59
+ return "";
60
60
  }
61
61
 
62
62
  export function highlightToAnsi(code: string, language?: string): string {
63
63
  if (!code) {
64
- return '';
64
+ return "";
65
65
  }
66
66
  try {
67
67
  const highlighted = language
68
68
  ? hljs.highlight(code, { language }).value
69
- : hljs.highlightAuto(code).value;
69
+ : hljs.highlightAuto(code, [
70
+ "javascript",
71
+ "typescript",
72
+ "bash",
73
+ "json",
74
+ "markdown",
75
+ "python",
76
+ "yaml",
77
+ "html",
78
+ "css",
79
+ "sql",
80
+ "xml",
81
+ "rust",
82
+ "go",
83
+ "java",
84
+ "cpp",
85
+ "c",
86
+ "csharp",
87
+ "php",
88
+ "ruby",
89
+ "swift",
90
+ "kotlin",
91
+ "toml",
92
+ "ini",
93
+ ]).value;
70
94
 
71
95
  const root = parse(highlighted);
72
- return root.childNodes.map(nodeToAnsi).join('');
96
+ return root.childNodes.map(nodeToAnsi).join("");
73
97
  } catch {
74
98
  return code;
75
99
  }
@@ -0,0 +1,164 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as path from "node:path";
3
+ import * as fs from "node:fs";
4
+ import { getGitRepoRoot, getDefaultRemoteBranch } from "wave-agent-sdk";
5
+
6
+ export interface WorktreeSession {
7
+ name: string;
8
+ path: string;
9
+ branch: string;
10
+ repoRoot: string;
11
+ hasUncommittedChanges: boolean;
12
+ hasNewCommits: boolean;
13
+ isNew: boolean;
14
+ }
15
+
16
+ export const WORKTREE_DIR = ".wave/worktrees";
17
+
18
+ /**
19
+ * Create a new git worktree
20
+ * @param name Worktree name
21
+ * @param cwd Current working directory
22
+ * @returns Worktree session details
23
+ */
24
+ export function createWorktree(name: string, cwd: string): WorktreeSession {
25
+ const repoRoot = getGitRepoRoot(cwd);
26
+ const worktreePath = path.join(repoRoot, WORKTREE_DIR, name);
27
+ const branchName = `worktree-${name}`;
28
+ const baseBranch = getDefaultRemoteBranch(cwd);
29
+
30
+ // Ensure parent directory exists
31
+ const parentDir = path.dirname(worktreePath);
32
+ if (!fs.existsSync(parentDir)) {
33
+ fs.mkdirSync(parentDir, { recursive: true });
34
+ }
35
+
36
+ // Check if worktree already exists
37
+ if (fs.existsSync(worktreePath)) {
38
+ // If it exists, we assume it's already set up correctly
39
+ return {
40
+ name,
41
+ path: worktreePath,
42
+ branch: branchName,
43
+ repoRoot,
44
+ hasUncommittedChanges: false,
45
+ hasNewCommits: false,
46
+ isNew: false,
47
+ };
48
+ }
49
+
50
+ try {
51
+ // Create worktree and branch
52
+ execSync(
53
+ `git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`,
54
+ {
55
+ cwd: repoRoot,
56
+ stdio: ["ignore", "pipe", "pipe"],
57
+ },
58
+ );
59
+
60
+ return {
61
+ name,
62
+ path: worktreePath,
63
+ branch: branchName,
64
+ repoRoot,
65
+ hasUncommittedChanges: false,
66
+ hasNewCommits: false,
67
+ isNew: true,
68
+ };
69
+ } catch (error: unknown) {
70
+ const stderr = (error as { stderr?: Buffer }).stderr?.toString() || "";
71
+ if (stderr.includes("already exists")) {
72
+ // If branch already exists, try to add worktree without -b
73
+ try {
74
+ execSync(`git worktree add "${worktreePath}" ${branchName}`, {
75
+ cwd: repoRoot,
76
+ stdio: ["ignore", "pipe", "pipe"],
77
+ });
78
+ return {
79
+ name,
80
+ path: worktreePath,
81
+ branch: branchName,
82
+ repoRoot,
83
+ hasUncommittedChanges: false,
84
+ hasNewCommits: false,
85
+ isNew: true,
86
+ };
87
+ } catch (innerError: unknown) {
88
+ throw new Error(
89
+ `Failed to add existing worktree branch: ${(innerError as Error).message}`,
90
+ );
91
+ }
92
+ }
93
+ throw new Error(
94
+ `Failed to create worktree: ${(error as Error).message}\n${stderr}`,
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Remove a git worktree and its associated branch
101
+ * @param session Worktree session details
102
+ */
103
+ export function removeWorktree(session: WorktreeSession): void {
104
+ const repoRoot = session.repoRoot;
105
+
106
+ try {
107
+ // Get current branch in worktree before removing it
108
+ let currentBranch: string | undefined;
109
+ try {
110
+ currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`, {
111
+ cwd: session.path,
112
+ encoding: "utf8",
113
+ stdio: ["ignore", "pipe", "ignore"],
114
+ }).trim();
115
+ } catch {
116
+ // Ignore errors getting current branch
117
+ }
118
+
119
+ // Remove worktree
120
+ execSync(`git worktree remove --force "${session.path}"`, {
121
+ cwd: repoRoot,
122
+ stdio: ["ignore", "pipe", "pipe"],
123
+ });
124
+
125
+ // Delete original branch
126
+ try {
127
+ execSync(`git branch -D ${session.branch}`, {
128
+ cwd: repoRoot,
129
+ stdio: ["ignore", "pipe", "pipe"],
130
+ });
131
+ } catch {
132
+ // Ignore errors deleting original branch
133
+ }
134
+
135
+ // Delete current branch if it's different and not a protected branch
136
+ if (
137
+ currentBranch &&
138
+ currentBranch !== session.branch &&
139
+ currentBranch !== "HEAD"
140
+ ) {
141
+ const defaultRemoteBranch = getDefaultRemoteBranch(repoRoot);
142
+ const defaultBranchName = defaultRemoteBranch.split("/").pop();
143
+
144
+ if (
145
+ currentBranch !== defaultBranchName &&
146
+ currentBranch !== "main" &&
147
+ currentBranch !== "master"
148
+ ) {
149
+ try {
150
+ execSync(`git branch -D ${currentBranch}`, {
151
+ cwd: repoRoot,
152
+ stdio: ["ignore", "pipe", "pipe"],
153
+ });
154
+ } catch {
155
+ // Ignore errors deleting current branch
156
+ }
157
+ }
158
+ }
159
+ } catch (error: unknown) {
160
+ console.error(
161
+ `Failed to remove worktree or branch: ${(error as Error).message}`,
162
+ );
163
+ }
164
+ }
@@ -1,9 +0,0 @@
1
- import React from "react";
2
- import type { CommandOutputBlock } from "wave-agent-sdk";
3
- interface CommandOutputDisplayProps {
4
- block: CommandOutputBlock;
5
- isExpanded?: boolean;
6
- }
7
- export declare const CommandOutputDisplay: React.FC<CommandOutputDisplayProps>;
8
- export {};
9
- //# sourceMappingURL=CommandOutputDisplay.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CommandOutputDisplay.d.ts","sourceRoot":"","sources":["../../src/components/CommandOutputDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,UAAU,yBAAyB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAiDpE,CAAC"}