wave-code 0.0.4 → 0.0.6

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 (102) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ChatInterface.d.ts.map +1 -1
  3. package/dist/components/ChatInterface.js +4 -24
  4. package/dist/components/CommandSelector.js +4 -4
  5. package/dist/components/DiffViewer.d.ts +1 -1
  6. package/dist/components/DiffViewer.d.ts.map +1 -1
  7. package/dist/components/DiffViewer.js +15 -15
  8. package/dist/components/FileSelector.js +2 -2
  9. package/dist/components/InputBox.d.ts.map +1 -1
  10. package/dist/components/InputBox.js +46 -101
  11. package/dist/components/Markdown.d.ts +6 -0
  12. package/dist/components/Markdown.d.ts.map +1 -0
  13. package/dist/components/Markdown.js +22 -0
  14. package/dist/components/MessageItem.d.ts +9 -0
  15. package/dist/components/MessageItem.d.ts.map +1 -0
  16. package/dist/components/MessageItem.js +15 -0
  17. package/dist/components/MessageList.d.ts +1 -1
  18. package/dist/components/MessageList.d.ts.map +1 -1
  19. package/dist/components/MessageList.js +33 -32
  20. package/dist/components/SubagentBlock.d.ts +1 -2
  21. package/dist/components/SubagentBlock.d.ts.map +1 -1
  22. package/dist/components/SubagentBlock.js +29 -20
  23. package/dist/components/ToolResultDisplay.js +5 -5
  24. package/dist/contexts/useChat.d.ts +1 -0
  25. package/dist/contexts/useChat.d.ts.map +1 -1
  26. package/dist/contexts/useChat.js +29 -2
  27. package/dist/hooks/useInputManager.d.ts +93 -0
  28. package/dist/hooks/useInputManager.d.ts.map +1 -0
  29. package/dist/hooks/useInputManager.js +332 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +17 -10
  32. package/dist/managers/InputManager.d.ts +171 -0
  33. package/dist/managers/InputManager.d.ts.map +1 -0
  34. package/dist/managers/InputManager.js +826 -0
  35. package/dist/print-cli.d.ts +8 -0
  36. package/dist/print-cli.d.ts.map +1 -0
  37. package/dist/print-cli.js +128 -0
  38. package/dist/utils/constants.d.ts +1 -1
  39. package/dist/utils/constants.js +1 -1
  40. package/dist/utils/fileSearch.d.ts +20 -0
  41. package/dist/utils/fileSearch.d.ts.map +1 -0
  42. package/dist/utils/fileSearch.js +102 -0
  43. package/dist/utils/logger.js +3 -3
  44. package/dist/utils/usageSummary.d.ts +33 -0
  45. package/dist/utils/usageSummary.d.ts.map +1 -0
  46. package/dist/utils/usageSummary.js +154 -0
  47. package/package.json +10 -6
  48. package/src/components/ChatInterface.tsx +13 -43
  49. package/src/components/CommandSelector.tsx +5 -5
  50. package/src/components/DiffViewer.tsx +18 -16
  51. package/src/components/FileSelector.tsx +2 -2
  52. package/src/components/InputBox.tsx +78 -169
  53. package/src/components/Markdown.tsx +29 -0
  54. package/src/components/MessageItem.tsx +104 -0
  55. package/src/components/MessageList.tsx +142 -198
  56. package/src/components/SubagentBlock.tsx +56 -73
  57. package/src/components/ToolResultDisplay.tsx +6 -6
  58. package/src/contexts/useChat.tsx +34 -2
  59. package/src/hooks/useInputManager.ts +461 -0
  60. package/src/index.ts +20 -10
  61. package/src/managers/InputManager.ts +1132 -0
  62. package/src/print-cli.ts +160 -0
  63. package/src/utils/constants.ts +1 -1
  64. package/src/utils/fileSearch.ts +133 -0
  65. package/src/utils/logger.ts +3 -3
  66. package/src/utils/usageSummary.ts +234 -0
  67. package/dist/hooks/useBashHistorySelector.d.ts +0 -15
  68. package/dist/hooks/useBashHistorySelector.d.ts.map +0 -1
  69. package/dist/hooks/useBashHistorySelector.js +0 -61
  70. package/dist/hooks/useCommandSelector.d.ts +0 -24
  71. package/dist/hooks/useCommandSelector.d.ts.map +0 -1
  72. package/dist/hooks/useCommandSelector.js +0 -98
  73. package/dist/hooks/useFileSelector.d.ts +0 -16
  74. package/dist/hooks/useFileSelector.d.ts.map +0 -1
  75. package/dist/hooks/useFileSelector.js +0 -174
  76. package/dist/hooks/useImageManager.d.ts +0 -13
  77. package/dist/hooks/useImageManager.d.ts.map +0 -1
  78. package/dist/hooks/useImageManager.js +0 -46
  79. package/dist/hooks/useInputHistory.d.ts +0 -11
  80. package/dist/hooks/useInputHistory.d.ts.map +0 -1
  81. package/dist/hooks/useInputHistory.js +0 -64
  82. package/dist/hooks/useInputKeyboardHandler.d.ts +0 -83
  83. package/dist/hooks/useInputKeyboardHandler.d.ts.map +0 -1
  84. package/dist/hooks/useInputKeyboardHandler.js +0 -507
  85. package/dist/hooks/useInputState.d.ts +0 -14
  86. package/dist/hooks/useInputState.d.ts.map +0 -1
  87. package/dist/hooks/useInputState.js +0 -57
  88. package/dist/hooks/useMemoryTypeSelector.d.ts +0 -9
  89. package/dist/hooks/useMemoryTypeSelector.d.ts.map +0 -1
  90. package/dist/hooks/useMemoryTypeSelector.js +0 -27
  91. package/dist/plain-cli.d.ts +0 -7
  92. package/dist/plain-cli.d.ts.map +0 -1
  93. package/dist/plain-cli.js +0 -44
  94. package/src/hooks/useBashHistorySelector.ts +0 -77
  95. package/src/hooks/useCommandSelector.ts +0 -131
  96. package/src/hooks/useFileSelector.ts +0 -227
  97. package/src/hooks/useImageManager.ts +0 -64
  98. package/src/hooks/useInputHistory.ts +0 -74
  99. package/src/hooks/useInputKeyboardHandler.ts +0 -778
  100. package/src/hooks/useInputState.ts +0 -66
  101. package/src/hooks/useMemoryTypeSelector.ts +0 -40
  102. package/src/plain-cli.ts +0 -60
@@ -0,0 +1,8 @@
1
+ export interface PrintCliOptions {
2
+ restoreSessionId?: string;
3
+ continueLastSession?: boolean;
4
+ message?: string;
5
+ showStats?: boolean;
6
+ }
7
+ export declare function startPrintCli(options: PrintCliOptions): Promise<void>;
8
+ //# sourceMappingURL=print-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuI3E"}
@@ -0,0 +1,128 @@
1
+ import { Agent } from "wave-agent-sdk";
2
+ import { displayUsageSummary } from "./utils/usageSummary.js";
3
+ function displayTimingInfo(startTime, showStats) {
4
+ // Skip timing info in test environment or if stats are disabled
5
+ if (process.env.NODE_ENV === "test" || process.env.VITEST || !showStats) {
6
+ return;
7
+ }
8
+ const endTime = new Date();
9
+ const duration = endTime.getTime() - startTime.getTime();
10
+ process.stdout.write(`\n\n📅 Start time: ${startTime.toISOString()}\n`);
11
+ process.stdout.write(`📅 End time: ${endTime.toISOString()}\n`);
12
+ process.stdout.write(`⏱️ Duration: ${duration}ms\n`);
13
+ }
14
+ export async function startPrintCli(options) {
15
+ const startTime = new Date();
16
+ const { restoreSessionId, continueLastSession, message, showStats = false, } = options;
17
+ if ((!message || message.trim() === "") &&
18
+ !continueLastSession &&
19
+ !restoreSessionId) {
20
+ console.error("Print mode requires a message: use --print 'your message' or -p 'your message'");
21
+ process.exit(1);
22
+ }
23
+ let agent;
24
+ // Setup callbacks for agent
25
+ const callbacks = {
26
+ onAssistantMessageAdded: () => {
27
+ // Assistant message started - no content to output yet
28
+ process.stdout.write("\n");
29
+ },
30
+ onAssistantContentUpdated: (chunk) => {
31
+ // FR-001: Stream content updates for real-time display - output only the new chunk
32
+ process.stdout.write(chunk);
33
+ },
34
+ // Tool block callback - display tool name when tool starts
35
+ onToolBlockUpdated: (params) => {
36
+ // Print tool name only during 'running' stage (happens once per tool call)
37
+ if (params.stage === "running" && params.name) {
38
+ process.stdout.write(`\n🔧 ${params.name}`);
39
+ if (params.compactParams) {
40
+ process.stdout.write(` ${params.compactParams}`);
41
+ }
42
+ process.stdout.write(`\n`);
43
+ }
44
+ },
45
+ // Subagent block callbacks
46
+ onSubAgentBlockAdded: (subagentId, parameters) => {
47
+ // Display subagent creation with indentation
48
+ process.stdout.write(`\n🤖 Subagent [${parameters.subagent_type}]: ${parameters.description}\n`);
49
+ },
50
+ onSubAgentBlockUpdated: (subagentId, status) => {
51
+ // Display subagent status updates
52
+ const statusIconMap = {
53
+ active: "🔄",
54
+ completed: "✅",
55
+ error: "❌",
56
+ aborted: "⚠️",
57
+ };
58
+ const statusIcon = statusIconMap[status] ?? "🔄";
59
+ process.stdout.write(` ${statusIcon} Subagent status: ${status}\n`);
60
+ },
61
+ // Subagent message callbacks
62
+ onSubagentAssistantMessageAdded: () => {
63
+ // Subagent assistant message started - add indentation
64
+ process.stdout.write("\n ");
65
+ },
66
+ onSubagentAssistantContentUpdated: (_subagentId, chunk) => {
67
+ // Stream subagent content with indentation - output only the new chunk
68
+ process.stdout.write(chunk);
69
+ },
70
+ onSubagentUserMessageAdded: (_subagentId, params) => {
71
+ // Display subagent user messages with indentation
72
+ process.stdout.write(`\n 👤 User: ${params.content}\n`);
73
+ },
74
+ // Error block callback
75
+ onErrorBlockAdded: (error) => {
76
+ // Display error blocks with distinct formatting
77
+ process.stdout.write(`\n❌ Error: ${error}\n`);
78
+ },
79
+ };
80
+ try {
81
+ // Initialize agent
82
+ agent = await Agent.create({
83
+ callbacks,
84
+ restoreSessionId,
85
+ continueLastSession,
86
+ });
87
+ // Send message if provided and not empty
88
+ if (message && message.trim() !== "") {
89
+ await agent.sendMessage(message);
90
+ }
91
+ // Display usage summary before exit
92
+ if (showStats) {
93
+ try {
94
+ const usages = agent.usages;
95
+ const sessionFilePath = agent.sessionFilePath;
96
+ displayUsageSummary(usages, sessionFilePath);
97
+ }
98
+ catch {
99
+ // Silently ignore usage summary errors
100
+ }
101
+ }
102
+ // Display timing information
103
+ displayTimingInfo(startTime, showStats);
104
+ // Destroy agent and exit after sendMessage completes
105
+ await agent.destroy();
106
+ process.exit(0);
107
+ }
108
+ catch (error) {
109
+ console.error("Failed to send message:", error);
110
+ if (agent) {
111
+ // Display usage summary even on error
112
+ if (showStats) {
113
+ try {
114
+ const usages = agent.usages;
115
+ const sessionFilePath = agent.sessionFilePath;
116
+ displayUsageSummary(usages, sessionFilePath);
117
+ }
118
+ catch {
119
+ // Silently ignore usage summary errors
120
+ }
121
+ }
122
+ // Display timing information even on error
123
+ displayTimingInfo(startTime, showStats);
124
+ await agent.destroy();
125
+ }
126
+ process.exit(1);
127
+ }
128
+ }
@@ -13,5 +13,5 @@ export declare const LOG_FILE: string;
13
13
  /**
14
14
  * Pagination related constants
15
15
  */
16
- export declare const MESSAGES_PER_PAGE = 20;
16
+ export declare const MESSAGES_PER_PAGE = 15;
17
17
  //# sourceMappingURL=constants.d.ts.map
@@ -15,4 +15,4 @@ export const LOG_FILE = path.join(DATA_DIRECTORY, "app.log");
15
15
  /**
16
16
  * Pagination related constants
17
17
  */
18
- export const MESSAGES_PER_PAGE = 20; // Number of messages displayed per page
18
+ export const MESSAGES_PER_PAGE = 15; // Number of messages displayed per page
@@ -0,0 +1,20 @@
1
+ export interface FileItem {
2
+ path: string;
3
+ type: "file" | "directory";
4
+ }
5
+ /**
6
+ * Check if path is a directory
7
+ */
8
+ export declare const isDirectory: (filePath: string) => boolean;
9
+ /**
10
+ * Convert string paths to FileItem objects
11
+ */
12
+ export declare const convertToFileItems: (paths: string[]) => FileItem[];
13
+ /**
14
+ * Search files and directories using glob patterns
15
+ */
16
+ export declare const searchFiles: (query: string, options?: {
17
+ maxResults?: number;
18
+ workingDirectory?: string;
19
+ }) => Promise<FileItem[]>;
20
+ //# sourceMappingURL=fileSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSearch.d.ts","sourceRoot":"","sources":["../../src/utils/fileSearch.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,KAAG,OAS9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,KAAG,QAAQ,EAK5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,UAAU;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,KACA,OAAO,CAAC,QAAQ,EAAE,CAyFpB,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { glob } from "glob";
2
+ import { getGlobIgnorePatterns } from "wave-agent-sdk";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ /**
6
+ * Check if path is a directory
7
+ */
8
+ export const isDirectory = (filePath) => {
9
+ try {
10
+ const fullPath = path.isAbsolute(filePath)
11
+ ? filePath
12
+ : path.join(process.cwd(), filePath);
13
+ return fs.statSync(fullPath).isDirectory();
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ };
19
+ /**
20
+ * Convert string paths to FileItem objects
21
+ */
22
+ export const convertToFileItems = (paths) => {
23
+ return paths.map((filePath) => ({
24
+ path: filePath,
25
+ type: isDirectory(filePath) ? "directory" : "file",
26
+ }));
27
+ };
28
+ /**
29
+ * Search files and directories using glob patterns
30
+ */
31
+ export const searchFiles = async (query, options) => {
32
+ const { maxResults = 10, workingDirectory = process.cwd() } = options || {};
33
+ try {
34
+ let files = [];
35
+ let directories = [];
36
+ const globOptions = {
37
+ ignore: getGlobIgnorePatterns(workingDirectory),
38
+ maxDepth: 10,
39
+ nocase: true, // Case insensitive
40
+ dot: true, // Include hidden files and directories
41
+ cwd: workingDirectory, // Specify search root directory
42
+ };
43
+ if (!query.trim()) {
44
+ // When query is empty, show some common file types and directories
45
+ const commonPatterns = [
46
+ "**/*.ts",
47
+ "**/*.tsx",
48
+ "**/*.js",
49
+ "**/*.jsx",
50
+ "**/*.json",
51
+ ];
52
+ // Search files
53
+ const filePromises = commonPatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: true }));
54
+ // Search directories (only search first level to avoid too many results)
55
+ const dirPromises = [glob("*/", { ...globOptions, maxDepth: 1 })];
56
+ const fileResults = await Promise.all(filePromises);
57
+ const dirResults = await Promise.all(dirPromises);
58
+ files = fileResults.flat();
59
+ directories = dirResults.flat().map((dir) => {
60
+ // glob returns string type paths, remove trailing slash
61
+ return String(dir).replace(/\/$/, "");
62
+ });
63
+ }
64
+ else {
65
+ // Build multiple glob patterns to support more flexible search
66
+ const filePatterns = [
67
+ // Match files with filenames containing query
68
+ `**/*${query}*`,
69
+ // Match files with query in path (match directory names)
70
+ `**/${query}*/**/*`,
71
+ ];
72
+ const dirPatterns = [
73
+ // Match directory names containing query
74
+ `**/*${query}*/`,
75
+ // Match directories containing query in path
76
+ `**/${query}*/`,
77
+ ];
78
+ // Search files
79
+ const filePromises = filePatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: true }));
80
+ // Search directories
81
+ const dirPromises = dirPatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: false }));
82
+ const fileResults = await Promise.all(filePromises);
83
+ const dirResults = await Promise.all(dirPromises);
84
+ files = fileResults.flat();
85
+ directories = dirResults.flat().map((dir) => {
86
+ // glob returns string type paths, remove trailing slash
87
+ return String(dir).replace(/\/$/, "");
88
+ });
89
+ }
90
+ // Deduplicate and merge files and directories
91
+ const uniqueFiles = Array.from(new Set(files));
92
+ const uniqueDirectories = Array.from(new Set(directories));
93
+ const allPaths = [...uniqueDirectories, ...uniqueFiles]; // Directories first
94
+ // Limit to maximum results and convert to FileItem
95
+ const fileItems = convertToFileItems(allPaths.slice(0, maxResults));
96
+ return fileItems;
97
+ }
98
+ catch (error) {
99
+ console.error("Glob search error:", error);
100
+ return [];
101
+ }
102
+ };
@@ -216,7 +216,7 @@ const truncateLogFileIfNeeded = (config) => {
216
216
  fs.writeFileSync(logFile, truncatedContent);
217
217
  // Record truncation operation
218
218
  const removedLines = lines.length - keepLines;
219
- logger.info(`Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`);
219
+ logger.debug(`Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`);
220
220
  }
221
221
  }
222
222
  catch (error) {
@@ -235,11 +235,11 @@ export const cleanupLogs = async (customConfig) => {
235
235
  return;
236
236
  }
237
237
  const config = { ...getCleanupConfig(), ...customConfig };
238
- logger.info("Starting log cleanup...", {
238
+ logger.debug("Starting log cleanup...", {
239
239
  maxFileSize: config.maxFileSize,
240
240
  keepLines: config.keepLines,
241
241
  });
242
242
  // Truncate current log file (if needed)
243
243
  truncateLogFileIfNeeded(config);
244
- logger.info("Log cleanup completed");
244
+ logger.debug("Log cleanup completed");
245
245
  };
@@ -0,0 +1,33 @@
1
+ import type { Usage } from "wave-agent-sdk";
2
+ /**
3
+ * Token summary by model
4
+ */
5
+ export interface TokenSummary {
6
+ model: string;
7
+ prompt_tokens: number;
8
+ completion_tokens: number;
9
+ total_tokens: number;
10
+ operations: {
11
+ agent_calls: number;
12
+ compressions: number;
13
+ };
14
+ cache_read_input_tokens?: number;
15
+ cache_creation_input_tokens?: number;
16
+ cache_creation?: {
17
+ ephemeral_5m_input_tokens: number;
18
+ ephemeral_1h_input_tokens: number;
19
+ };
20
+ }
21
+ /**
22
+ * Calculate token usage summary by model from usage array
23
+ * @param usages Array of usage data from agent operations
24
+ * @returns Array of token summaries grouped by model
25
+ */
26
+ export declare function calculateTokenSummary(usages: Usage[]): Record<string, TokenSummary>;
27
+ /**
28
+ * Display usage summary in a formatted way
29
+ * @param usages Array of usage data from agent operations
30
+ * @param sessionFilePath Optional session file path to display
31
+ */
32
+ export declare function displayUsageSummary(usages: Usage[], sessionFilePath?: string): void;
33
+ //# sourceMappingURL=usageSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usageSummary.d.ts","sourceRoot":"","sources":["../../src/utils/usageSummary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,cAAc,CAAC,EAAE;QACf,yBAAyB,EAAE,MAAM,CAAC;QAClC,yBAAyB,EAAE,MAAM,CAAC;KACnC,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,KAAK,EAAE,GACd,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAyE9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,KAAK,EAAE,EACf,eAAe,CAAC,EAAE,MAAM,GACvB,IAAI,CAwHN"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Calculate token usage summary by model from usage array
3
+ * @param usages Array of usage data from agent operations
4
+ * @returns Array of token summaries grouped by model
5
+ */
6
+ export function calculateTokenSummary(usages) {
7
+ const summaryMap = new Map();
8
+ for (const usage of usages) {
9
+ const model = usage.model || "unknown";
10
+ if (!summaryMap.has(model)) {
11
+ summaryMap.set(model, {
12
+ model,
13
+ prompt_tokens: 0,
14
+ completion_tokens: 0,
15
+ total_tokens: 0,
16
+ operations: {
17
+ agent_calls: 0,
18
+ compressions: 0,
19
+ },
20
+ });
21
+ }
22
+ const summary = summaryMap.get(model);
23
+ summary.prompt_tokens += usage.prompt_tokens;
24
+ summary.completion_tokens += usage.completion_tokens;
25
+ summary.total_tokens += usage.total_tokens;
26
+ // Handle cache tokens if present and non-zero
27
+ if (usage.cache_read_input_tokens && usage.cache_read_input_tokens > 0) {
28
+ summary.cache_read_input_tokens =
29
+ (summary.cache_read_input_tokens || 0) + usage.cache_read_input_tokens;
30
+ }
31
+ if (usage.cache_creation_input_tokens &&
32
+ usage.cache_creation_input_tokens > 0) {
33
+ summary.cache_creation_input_tokens =
34
+ (summary.cache_creation_input_tokens || 0) +
35
+ usage.cache_creation_input_tokens;
36
+ }
37
+ if (usage.cache_creation &&
38
+ (usage.cache_creation.ephemeral_5m_input_tokens > 0 ||
39
+ usage.cache_creation.ephemeral_1h_input_tokens > 0)) {
40
+ if (!summary.cache_creation) {
41
+ summary.cache_creation = {
42
+ ephemeral_5m_input_tokens: 0,
43
+ ephemeral_1h_input_tokens: 0,
44
+ };
45
+ }
46
+ summary.cache_creation.ephemeral_5m_input_tokens +=
47
+ usage.cache_creation.ephemeral_5m_input_tokens || 0;
48
+ summary.cache_creation.ephemeral_1h_input_tokens +=
49
+ usage.cache_creation.ephemeral_1h_input_tokens || 0;
50
+ }
51
+ // Track operation types
52
+ if (usage.operation_type === "agent") {
53
+ summary.operations.agent_calls += 1;
54
+ }
55
+ else if (usage.operation_type === "compress") {
56
+ summary.operations.compressions += 1;
57
+ }
58
+ }
59
+ // Convert Map to Record and sort by total tokens
60
+ const result = {};
61
+ const sortedEntries = Array.from(summaryMap.entries()).sort((a, b) => b[1].total_tokens - a[1].total_tokens);
62
+ for (const [model, summary] of sortedEntries) {
63
+ result[model] = summary;
64
+ }
65
+ return result;
66
+ }
67
+ /**
68
+ * Display usage summary in a formatted way
69
+ * @param usages Array of usage data from agent operations
70
+ * @param sessionFilePath Optional session file path to display
71
+ */
72
+ export function displayUsageSummary(usages, sessionFilePath) {
73
+ if (usages.length === 0) {
74
+ return; // No usage data to display
75
+ }
76
+ const summaries = calculateTokenSummary(usages);
77
+ console.log("\nToken Usage Summary:");
78
+ console.log("==================");
79
+ if (sessionFilePath) {
80
+ console.log(`Session: ${sessionFilePath}`);
81
+ }
82
+ let totalPrompt = 0;
83
+ let totalCompletion = 0;
84
+ let totalTokens = 0;
85
+ let totalAgentCalls = 0;
86
+ let totalCompressions = 0;
87
+ let totalCacheRead = 0;
88
+ let totalCacheCreation = 0;
89
+ let totalCache5m = 0;
90
+ let totalCache1h = 0;
91
+ let hasCacheData = false;
92
+ for (const [, summary] of Object.entries(summaries)) {
93
+ console.log(`Model: ${summary.model}`);
94
+ console.log(` Prompt tokens: ${summary.prompt_tokens.toLocaleString()}`);
95
+ console.log(` Completion tokens: ${summary.completion_tokens.toLocaleString()}`);
96
+ console.log(` Total tokens: ${summary.total_tokens.toLocaleString()}`);
97
+ // Display cache information if available
98
+ if (summary.cache_read_input_tokens ||
99
+ summary.cache_creation_input_tokens ||
100
+ summary.cache_creation) {
101
+ hasCacheData = true;
102
+ console.log(" Cache Usage:");
103
+ if (summary.cache_read_input_tokens &&
104
+ summary.cache_read_input_tokens > 0) {
105
+ console.log(` Read from cache: ${summary.cache_read_input_tokens.toLocaleString()} tokens`);
106
+ totalCacheRead += summary.cache_read_input_tokens;
107
+ }
108
+ if (summary.cache_creation_input_tokens &&
109
+ summary.cache_creation_input_tokens > 0) {
110
+ console.log(` Created cache: ${summary.cache_creation_input_tokens.toLocaleString()} tokens`);
111
+ totalCacheCreation += summary.cache_creation_input_tokens;
112
+ }
113
+ if (summary.cache_creation) {
114
+ if (summary.cache_creation.ephemeral_5m_input_tokens > 0) {
115
+ console.log(` 5m cache: ${summary.cache_creation.ephemeral_5m_input_tokens.toLocaleString()} tokens`);
116
+ totalCache5m += summary.cache_creation.ephemeral_5m_input_tokens;
117
+ }
118
+ if (summary.cache_creation.ephemeral_1h_input_tokens > 0) {
119
+ console.log(` 1h cache: ${summary.cache_creation.ephemeral_1h_input_tokens.toLocaleString()} tokens`);
120
+ totalCache1h += summary.cache_creation.ephemeral_1h_input_tokens;
121
+ }
122
+ }
123
+ }
124
+ console.log(` Operations: ${summary.operations.agent_calls} agent calls, ${summary.operations.compressions} compressions`);
125
+ console.log();
126
+ totalPrompt += summary.prompt_tokens;
127
+ totalCompletion += summary.completion_tokens;
128
+ totalTokens += summary.total_tokens;
129
+ totalAgentCalls += summary.operations.agent_calls;
130
+ totalCompressions += summary.operations.compressions;
131
+ }
132
+ if (Object.keys(summaries).length > 1) {
133
+ console.log("Overall Total:");
134
+ console.log(` Prompt tokens: ${totalPrompt.toLocaleString()}`);
135
+ console.log(` Completion tokens: ${totalCompletion.toLocaleString()}`);
136
+ console.log(` Total tokens: ${totalTokens.toLocaleString()}`);
137
+ if (hasCacheData) {
138
+ console.log(" Cache Usage:");
139
+ if (totalCacheRead > 0) {
140
+ console.log(` Read from cache: ${totalCacheRead.toLocaleString()} tokens`);
141
+ }
142
+ if (totalCacheCreation > 0) {
143
+ console.log(` Created cache: ${totalCacheCreation.toLocaleString()} tokens`);
144
+ }
145
+ if (totalCache5m > 0) {
146
+ console.log(` 5m cache: ${totalCache5m.toLocaleString()} tokens`);
147
+ }
148
+ if (totalCache1h > 0) {
149
+ console.log(` 1h cache: ${totalCache1h.toLocaleString()} tokens`);
150
+ }
151
+ }
152
+ console.log(` Operations: ${totalAgentCalls} agent calls, ${totalCompressions} compressions`);
153
+ }
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "keywords": [
6
6
  "ai",
@@ -24,14 +24,18 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "ink": "^6.0.1",
28
- "react": "^19.1.0",
29
- "yargs": "^17.7.2",
27
+ "chalk": "^5.6.2",
30
28
  "diff": "^8.0.2",
31
29
  "glob": "^11.0.3",
32
- "wave-agent-sdk": "0.0.4"
30
+ "ink": "^6.5.1",
31
+ "marked": "^11.2.0",
32
+ "marked-terminal": "^7.3.0",
33
+ "react": "^19.1.0",
34
+ "yargs": "^17.7.2",
35
+ "wave-agent-sdk": "0.0.8"
33
36
  },
34
37
  "devDependencies": {
38
+ "@types/marked-terminal": "^6.1.1",
35
39
  "@types/react": "^19.1.8",
36
40
  "@types/yargs": "^17.0.0",
37
41
  "eslint-plugin-react": "^7.37.5",
@@ -52,7 +56,7 @@
52
56
  "scripts": {
53
57
  "build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
54
58
  "type-check": "tsc --noEmit --incremental",
55
- "dev": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
59
+ "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
56
60
  "test": "vitest run",
57
61
  "lint": "eslint --cache",
58
62
  "format": "prettier --write ."
@@ -1,9 +1,8 @@
1
- import React, { useRef, useEffect } from "react";
1
+ import React from "react";
2
2
  import { Box } from "ink";
3
3
  import { MessageList } from "./MessageList.js";
4
4
  import { InputBox } from "./InputBox.js";
5
5
  import { useChat } from "../contexts/useChat.js";
6
- import type { Message } from "wave-agent-sdk";
7
6
 
8
7
  export const ChatInterface: React.FC = () => {
9
8
  const {
@@ -19,54 +18,25 @@ export const ChatInterface: React.FC = () => {
19
18
  connectMcpServer,
20
19
  disconnectMcpServer,
21
20
  isExpanded,
21
+ sessionId,
22
22
  latestTotalTokens,
23
23
  slashCommands,
24
24
  hasSlashCommand,
25
25
  } = useChat();
26
26
 
27
- // Create a ref to store messages in expanded mode
28
- const expandedMessagesRef = useRef<Message[]>([]);
29
-
30
- useEffect(() => {
31
- // Only sync when collapsed
32
- if (!isExpanded) {
33
- expandedMessagesRef.current = messages.map((message, index) => {
34
- // If it's the last message, deep copy its blocks
35
- if (index === messages.length - 1) {
36
- return {
37
- ...message,
38
- blocks: message.blocks.map((block) => ({ ...block })),
39
- };
40
- }
41
- return message;
42
- });
43
- }
44
- }, [isExpanded, messages]);
27
+ if (!sessionId) return null;
45
28
 
46
29
  return (
47
- <Box flexDirection="column" height="100%">
48
- <Box flexGrow={1} flexDirection="column" paddingX={1}>
49
- {isExpanded ? (
50
- // Expanded mode uses messages from ref, loading and tokens are hardcoded to false and 0
51
- <MessageList
52
- messages={expandedMessagesRef.current}
53
- isLoading={false}
54
- isCommandRunning={false}
55
- latestTotalTokens={0}
56
- isExpanded={true}
57
- />
58
- ) : (
59
- // Normal mode uses real-time state
60
- <MessageList
61
- messages={messages}
62
- isLoading={isLoading}
63
- isCommandRunning={isCommandRunning}
64
- isCompressing={isCompressing}
65
- latestTotalTokens={latestTotalTokens}
66
- isExpanded={false}
67
- />
68
- )}
69
- </Box>
30
+ <Box flexDirection="column" height="100%" paddingY={1}>
31
+ <MessageList
32
+ messages={messages}
33
+ isLoading={isLoading}
34
+ isCommandRunning={isCommandRunning}
35
+ isCompressing={isCompressing}
36
+ latestTotalTokens={latestTotalTokens}
37
+ isExpanded={isExpanded}
38
+ key={String(isExpanded) + sessionId}
39
+ />
70
40
 
71
41
  {!isExpanded && (
72
42
  <InputBox
@@ -41,7 +41,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
41
41
  const filteredCommands = allCommands.filter(
42
42
  (command) =>
43
43
  !searchQuery ||
44
- command.name.toLowerCase().includes(searchQuery.toLowerCase()),
44
+ command.id.toLowerCase().includes(searchQuery.toLowerCase()),
45
45
  );
46
46
 
47
47
  useInput((input, key) => {
@@ -50,7 +50,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
50
50
  filteredCommands.length > 0 &&
51
51
  selectedIndex < filteredCommands.length
52
52
  ) {
53
- const selectedCommand = filteredCommands[selectedIndex].name;
53
+ const selectedCommand = filteredCommands[selectedIndex].id;
54
54
  onSelect(selectedCommand);
55
55
  }
56
56
  return;
@@ -61,7 +61,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
61
61
  filteredCommands.length > 0 &&
62
62
  selectedIndex < filteredCommands.length
63
63
  ) {
64
- const selectedCommand = filteredCommands[selectedIndex].name;
64
+ const selectedCommand = filteredCommands[selectedIndex].id;
65
65
  onInsert(selectedCommand);
66
66
  }
67
67
  return;
@@ -116,12 +116,12 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
116
116
  </Box>
117
117
 
118
118
  {filteredCommands.map((command, index) => (
119
- <Box key={command.name} flexDirection="column">
119
+ <Box key={command.id} flexDirection="column">
120
120
  <Text
121
121
  color={index === selectedIndex ? "black" : "white"}
122
122
  backgroundColor={index === selectedIndex ? "magenta" : undefined}
123
123
  >
124
- {index === selectedIndex ? "▶ " : " "}/{command.name}
124
+ {index === selectedIndex ? "▶ " : " "}/{command.id}
125
125
  </Text>
126
126
  {index === selectedIndex && (
127
127
  <Box marginLeft={4}>