agent-world 0.11.1 → 0.12.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.
Files changed (267) hide show
  1. package/README.md +17 -7
  2. package/dist/cli/commands.d.ts +109 -0
  3. package/dist/cli/commands.js +2024 -0
  4. package/dist/cli/display.d.ts +124 -0
  5. package/dist/cli/display.js +381 -0
  6. package/dist/cli/hitl.d.ts +33 -0
  7. package/dist/cli/hitl.js +81 -0
  8. package/dist/cli/index.d.ts +2 -0
  9. package/dist/cli/stream.d.ts +41 -0
  10. package/dist/cli/stream.js +222 -0
  11. package/dist/core/activity-tracker.d.ts +16 -0
  12. package/dist/core/activity-tracker.d.ts.map +1 -0
  13. package/dist/core/activity-tracker.js +91 -0
  14. package/dist/core/activity-tracker.js.map +1 -0
  15. package/dist/core/ai-commands.d.ts +16 -0
  16. package/dist/core/ai-commands.d.ts.map +1 -0
  17. package/dist/core/ai-commands.js +24 -0
  18. package/dist/core/ai-commands.js.map +1 -0
  19. package/dist/core/ai-sdk-patch.d.ts +24 -0
  20. package/dist/core/ai-sdk-patch.d.ts.map +1 -0
  21. package/dist/core/ai-sdk-patch.js +169 -0
  22. package/dist/core/ai-sdk-patch.js.map +1 -0
  23. package/dist/core/anthropic-direct.d.ts +52 -0
  24. package/dist/core/anthropic-direct.d.ts.map +1 -0
  25. package/dist/core/anthropic-direct.js +301 -0
  26. package/dist/core/anthropic-direct.js.map +1 -0
  27. package/dist/core/approval-cache.d.ts +104 -0
  28. package/dist/core/approval-cache.d.ts.map +1 -0
  29. package/dist/core/approval-cache.js +150 -0
  30. package/dist/core/approval-cache.js.map +1 -0
  31. package/dist/core/chat-constants.d.ts +20 -0
  32. package/dist/core/chat-constants.d.ts.map +1 -0
  33. package/dist/core/chat-constants.js +22 -0
  34. package/dist/core/chat-constants.js.map +1 -0
  35. package/dist/core/create-agent-tool.d.ts +66 -0
  36. package/dist/core/create-agent-tool.d.ts.map +1 -0
  37. package/dist/core/create-agent-tool.js +212 -0
  38. package/dist/core/create-agent-tool.js.map +1 -0
  39. package/dist/core/events/approval-checker.d.ts +61 -0
  40. package/dist/core/events/approval-checker.d.ts.map +1 -0
  41. package/dist/core/events/approval-checker.js +226 -0
  42. package/dist/core/events/approval-checker.js.map +1 -0
  43. package/dist/core/events/index.d.ts +25 -0
  44. package/dist/core/events/index.d.ts.map +1 -0
  45. package/dist/core/events/index.js +30 -0
  46. package/dist/core/events/index.js.map +1 -0
  47. package/dist/core/events/memory-manager.d.ts +73 -0
  48. package/dist/core/events/memory-manager.d.ts.map +1 -0
  49. package/dist/core/events/memory-manager.js +1218 -0
  50. package/dist/core/events/memory-manager.js.map +1 -0
  51. package/dist/core/events/mention-logic.d.ts +39 -0
  52. package/dist/core/events/mention-logic.d.ts.map +1 -0
  53. package/dist/core/events/mention-logic.js +163 -0
  54. package/dist/core/events/mention-logic.js.map +1 -0
  55. package/dist/core/events/orchestrator.d.ts +69 -0
  56. package/dist/core/events/orchestrator.d.ts.map +1 -0
  57. package/dist/core/events/orchestrator.js +883 -0
  58. package/dist/core/events/orchestrator.js.map +1 -0
  59. package/dist/core/events/persistence.d.ts +41 -0
  60. package/dist/core/events/persistence.d.ts.map +1 -0
  61. package/dist/core/events/persistence.js +296 -0
  62. package/dist/core/events/persistence.js.map +1 -0
  63. package/dist/core/events/publishers.d.ts +81 -0
  64. package/dist/core/events/publishers.d.ts.map +1 -0
  65. package/dist/core/events/publishers.js +272 -0
  66. package/dist/core/events/publishers.js.map +1 -0
  67. package/dist/core/events/subscribers.d.ts +45 -0
  68. package/dist/core/events/subscribers.d.ts.map +1 -0
  69. package/dist/core/events/subscribers.js +288 -0
  70. package/dist/core/events/subscribers.js.map +1 -0
  71. package/dist/core/events/tool-bridge-logging.d.ts +28 -0
  72. package/dist/core/events/tool-bridge-logging.d.ts.map +1 -0
  73. package/dist/core/events/tool-bridge-logging.js +94 -0
  74. package/dist/core/events/tool-bridge-logging.js.map +1 -0
  75. package/dist/core/events-metadata.d.ts +72 -0
  76. package/dist/core/events-metadata.d.ts.map +1 -0
  77. package/dist/core/events-metadata.js +167 -0
  78. package/dist/core/events-metadata.js.map +1 -0
  79. package/dist/core/events.d.ts +186 -0
  80. package/dist/core/events.d.ts.map +1 -0
  81. package/dist/core/events.js +1248 -0
  82. package/dist/core/events.js.map +1 -0
  83. package/dist/core/export.d.ts +106 -0
  84. package/dist/core/export.d.ts.map +1 -0
  85. package/dist/core/export.js +705 -0
  86. package/dist/core/export.js.map +1 -0
  87. package/dist/core/file-tools.d.ts +114 -0
  88. package/dist/core/file-tools.d.ts.map +1 -0
  89. package/dist/core/file-tools.js +370 -0
  90. package/dist/core/file-tools.js.map +1 -0
  91. package/dist/core/google-direct.d.ts +58 -0
  92. package/dist/core/google-direct.d.ts.map +1 -0
  93. package/dist/core/google-direct.js +298 -0
  94. package/dist/core/google-direct.js.map +1 -0
  95. package/dist/core/hitl.d.ts +54 -0
  96. package/dist/core/hitl.d.ts.map +1 -0
  97. package/dist/core/hitl.js +153 -0
  98. package/dist/core/hitl.js.map +1 -0
  99. package/dist/core/index.d.ts +59 -0
  100. package/dist/core/index.d.ts.map +1 -0
  101. package/dist/core/index.js +70 -0
  102. package/dist/core/index.js.map +1 -0
  103. package/dist/core/llm-config.d.ts +128 -0
  104. package/dist/core/llm-config.d.ts.map +1 -0
  105. package/dist/core/llm-config.js +164 -0
  106. package/dist/core/llm-config.js.map +1 -0
  107. package/dist/core/llm-manager.d.ts +163 -0
  108. package/dist/core/llm-manager.d.ts.map +1 -0
  109. package/dist/core/llm-manager.js +669 -0
  110. package/dist/core/llm-manager.js.map +1 -0
  111. package/dist/core/load-skill-tool.d.ts +55 -0
  112. package/dist/core/load-skill-tool.d.ts.map +1 -0
  113. package/dist/core/load-skill-tool.js +468 -0
  114. package/dist/core/load-skill-tool.js.map +1 -0
  115. package/dist/core/logger.d.ts +88 -0
  116. package/dist/core/logger.d.ts.map +1 -0
  117. package/dist/core/logger.js +358 -0
  118. package/dist/core/logger.js.map +1 -0
  119. package/dist/core/managers.d.ts +131 -0
  120. package/dist/core/managers.d.ts.map +1 -0
  121. package/dist/core/managers.js +1223 -0
  122. package/dist/core/managers.js.map +1 -0
  123. package/dist/core/mcp-server-registry.d.ts +304 -0
  124. package/dist/core/mcp-server-registry.d.ts.map +1 -0
  125. package/dist/core/mcp-server-registry.js +1769 -0
  126. package/dist/core/mcp-server-registry.js.map +1 -0
  127. package/dist/core/mcp-tools.d.ts +56 -0
  128. package/dist/core/mcp-tools.d.ts.map +1 -0
  129. package/dist/core/mcp-tools.js +186 -0
  130. package/dist/core/mcp-tools.js.map +1 -0
  131. package/dist/core/message-prep.d.ts +81 -0
  132. package/dist/core/message-prep.d.ts.map +1 -0
  133. package/dist/core/message-prep.js +223 -0
  134. package/dist/core/message-prep.js.map +1 -0
  135. package/dist/core/message-processing-control.d.ts +54 -0
  136. package/dist/core/message-processing-control.d.ts.map +1 -0
  137. package/dist/core/message-processing-control.js +139 -0
  138. package/dist/core/message-processing-control.js.map +1 -0
  139. package/dist/core/openai-direct.d.ts +80 -0
  140. package/dist/core/openai-direct.d.ts.map +1 -0
  141. package/dist/core/openai-direct.js +374 -0
  142. package/dist/core/openai-direct.js.map +1 -0
  143. package/dist/core/shell-cmd-tool.d.ts +235 -0
  144. package/dist/core/shell-cmd-tool.d.ts.map +1 -0
  145. package/dist/core/shell-cmd-tool.js +1157 -0
  146. package/dist/core/shell-cmd-tool.js.map +1 -0
  147. package/dist/core/shell-process-registry.d.ts +88 -0
  148. package/dist/core/shell-process-registry.d.ts.map +1 -0
  149. package/dist/core/shell-process-registry.js +309 -0
  150. package/dist/core/shell-process-registry.js.map +1 -0
  151. package/dist/core/skill-registry.d.ts +75 -0
  152. package/dist/core/skill-registry.d.ts.map +1 -0
  153. package/dist/core/skill-registry.js +369 -0
  154. package/dist/core/skill-registry.js.map +1 -0
  155. package/dist/core/skill-script-runner.d.ts +89 -0
  156. package/dist/core/skill-script-runner.d.ts.map +1 -0
  157. package/dist/core/skill-script-runner.js +274 -0
  158. package/dist/core/skill-script-runner.js.map +1 -0
  159. package/dist/core/skill-selector.d.ts +65 -0
  160. package/dist/core/skill-selector.d.ts.map +1 -0
  161. package/dist/core/skill-selector.js +190 -0
  162. package/dist/core/skill-selector.js.map +1 -0
  163. package/dist/core/skill-settings.d.ts +20 -0
  164. package/dist/core/skill-settings.d.ts.map +1 -0
  165. package/dist/core/skill-settings.js +40 -0
  166. package/dist/core/skill-settings.js.map +1 -0
  167. package/dist/core/storage/agent-storage.d.ts +134 -0
  168. package/dist/core/storage/agent-storage.d.ts.map +1 -0
  169. package/dist/core/storage/agent-storage.js +498 -0
  170. package/dist/core/storage/agent-storage.js.map +1 -0
  171. package/dist/core/storage/eventStorage/fileEventStorage.d.ts +100 -0
  172. package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -0
  173. package/dist/core/storage/eventStorage/fileEventStorage.js +494 -0
  174. package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -0
  175. package/dist/core/storage/eventStorage/index.d.ts +31 -0
  176. package/dist/core/storage/eventStorage/index.d.ts.map +1 -0
  177. package/dist/core/storage/eventStorage/index.js +31 -0
  178. package/dist/core/storage/eventStorage/index.js.map +1 -0
  179. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +87 -0
  180. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -0
  181. package/dist/core/storage/eventStorage/memoryEventStorage.js +244 -0
  182. package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -0
  183. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +45 -0
  184. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -0
  185. package/dist/core/storage/eventStorage/sqliteEventStorage.js +301 -0
  186. package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -0
  187. package/dist/core/storage/eventStorage/types.d.ts +142 -0
  188. package/dist/core/storage/eventStorage/types.d.ts.map +1 -0
  189. package/dist/core/storage/eventStorage/types.js +43 -0
  190. package/dist/core/storage/eventStorage/types.js.map +1 -0
  191. package/dist/core/storage/eventStorage/validation.d.ts +30 -0
  192. package/dist/core/storage/eventStorage/validation.d.ts.map +1 -0
  193. package/dist/core/storage/eventStorage/validation.js +68 -0
  194. package/dist/core/storage/eventStorage/validation.js.map +1 -0
  195. package/dist/core/storage/legacy-migrations.d.ts +45 -0
  196. package/dist/core/storage/legacy-migrations.d.ts.map +1 -0
  197. package/dist/core/storage/legacy-migrations.js +295 -0
  198. package/dist/core/storage/legacy-migrations.js.map +1 -0
  199. package/dist/core/storage/memory-storage.d.ts +105 -0
  200. package/dist/core/storage/memory-storage.d.ts.map +1 -0
  201. package/dist/core/storage/memory-storage.js +415 -0
  202. package/dist/core/storage/memory-storage.js.map +1 -0
  203. package/dist/core/storage/migration-runner.d.ts +96 -0
  204. package/dist/core/storage/migration-runner.d.ts.map +1 -0
  205. package/dist/core/storage/migration-runner.js +306 -0
  206. package/dist/core/storage/migration-runner.js.map +1 -0
  207. package/dist/core/storage/queue-storage.d.ts +147 -0
  208. package/dist/core/storage/queue-storage.d.ts.map +1 -0
  209. package/dist/core/storage/queue-storage.js +290 -0
  210. package/dist/core/storage/queue-storage.js.map +1 -0
  211. package/dist/core/storage/skill-storage.d.ts +136 -0
  212. package/dist/core/storage/skill-storage.d.ts.map +1 -0
  213. package/dist/core/storage/skill-storage.js +474 -0
  214. package/dist/core/storage/skill-storage.js.map +1 -0
  215. package/dist/core/storage/sqlite-schema.d.ts +95 -0
  216. package/dist/core/storage/sqlite-schema.d.ts.map +1 -0
  217. package/dist/core/storage/sqlite-schema.js +156 -0
  218. package/dist/core/storage/sqlite-schema.js.map +1 -0
  219. package/dist/core/storage/sqlite-storage.d.ts +146 -0
  220. package/dist/core/storage/sqlite-storage.d.ts.map +1 -0
  221. package/dist/core/storage/sqlite-storage.js +709 -0
  222. package/dist/core/storage/sqlite-storage.js.map +1 -0
  223. package/dist/core/storage/storage-factory.d.ts +61 -0
  224. package/dist/core/storage/storage-factory.d.ts.map +1 -0
  225. package/dist/core/storage/storage-factory.js +794 -0
  226. package/dist/core/storage/storage-factory.js.map +1 -0
  227. package/dist/core/storage/validation.d.ts +36 -0
  228. package/dist/core/storage/validation.d.ts.map +1 -0
  229. package/dist/core/storage/validation.js +79 -0
  230. package/dist/core/storage/validation.js.map +1 -0
  231. package/dist/core/storage/world-storage.d.ts +114 -0
  232. package/dist/core/storage/world-storage.d.ts.map +1 -0
  233. package/dist/core/storage/world-storage.js +378 -0
  234. package/dist/core/storage/world-storage.js.map +1 -0
  235. package/dist/core/subscription.d.ts +43 -0
  236. package/dist/core/subscription.d.ts.map +1 -0
  237. package/dist/core/subscription.js +227 -0
  238. package/dist/core/subscription.js.map +1 -0
  239. package/dist/core/tool-utils.d.ts +80 -0
  240. package/dist/core/tool-utils.d.ts.map +1 -0
  241. package/dist/core/tool-utils.js +273 -0
  242. package/dist/core/tool-utils.js.map +1 -0
  243. package/dist/core/types.d.ts +595 -0
  244. package/dist/core/types.d.ts.map +1 -0
  245. package/dist/core/types.js +158 -0
  246. package/dist/core/types.js.map +1 -0
  247. package/dist/core/utils.d.ts +138 -0
  248. package/dist/core/utils.d.ts.map +1 -0
  249. package/dist/core/utils.js +478 -0
  250. package/dist/core/utils.js.map +1 -0
  251. package/dist/core/world-class.d.ts +43 -0
  252. package/dist/core/world-class.d.ts.map +1 -0
  253. package/dist/core/world-class.js +90 -0
  254. package/dist/core/world-class.js.map +1 -0
  255. package/dist/index.d.ts +18 -0
  256. package/dist/public/assets/agent-sprites-DJFgj-zP.png +0 -0
  257. package/dist/public/assets/border-KHK37r8y.svg +83 -0
  258. package/dist/public/assets/index-C9kPXL6G.css +1 -0
  259. package/dist/public/assets/index-DOQEHGWt.js +96 -0
  260. package/dist/public/index.html +21 -0
  261. package/dist/server/api.d.ts +2 -0
  262. package/dist/server/api.js +1124 -0
  263. package/dist/server/index.d.ts +29 -0
  264. package/dist/server/sse-handler.d.ts +62 -0
  265. package/dist/server/sse-handler.js +234 -0
  266. package/package.json +15 -3
  267. package/scripts/launch-electron.js +0 -58
@@ -0,0 +1,2024 @@
1
+ /**
2
+ * CLI Commands Implementation - Direct Core Integration with Space-Separated and Bidirectional Aliases
3
+ *
4
+ * Features:
5
+ * - Direct command mapping system with interactive parameter collection
6
+ * - Core function calls without command processing layer
7
+ * - User-friendly messages with technical details for debugging
8
+ * - Automatic world state management and refreshing
9
+ * - Help message generation with command documentation
10
+ * - Dual input handling for commands and messages
11
+ * - World instance isolation and proper cleanup during refresh
12
+ * - Space-separated command aliases for natural command syntax
13
+ * - Bidirectional aliases (e.g., /chat new and /new chat both work)
14
+ * - Backward compatibility with hyphenated aliases
15
+ * - Context-sensitive commands that adapt based on world selection
16
+ * - World data persistence to File Storage or SQLite with folder selection
17
+ *
18
+ * Available Commands:
19
+ * - World: list, show, create, update, delete, select, export, save
20
+ * - Agent: list, show, create, update, delete, clear
21
+ * - Chat: list, create, select, switch, delete, rename, export
22
+ * - System: help, quit, exit
23
+ * - Short Explicit: lsw (list worlds), lsa (list agents)
24
+ *
25
+ * Alias System:
26
+ * - Space-separated format: /new chat, /add agent, /list worlds (preferred)
27
+ * - Bidirectional aliases: /chat new = /new chat, /agent add = /add agent
28
+ * - Backward compatible: /new-chat, /add-agent, /list-worlds (still work)
29
+ * - Context-sensitive: /new creates world, /add creates agent (when world selected)
30
+ * - Explicit aliases like /lsw, /lsa provide unambiguous targeting
31
+ *
32
+ * World Refresh Mechanism:
33
+ * - Commands that modify world state signal refresh requirement via `refreshWorld: true`
34
+ * - CLI properly destroys old world instances and creates fresh ones
35
+ * - Event subscriptions are cleanly transferred to new world instances
36
+ * - Prevents memory leaks and ensures event isolation between old/new worlds
37
+ * - Agent persistence maintained across refresh cycles
38
+ *
39
+ * World Save Feature:
40
+ * - Interactive storage type selection (File or SQLite)
41
+ * - Custom folder path with default option
42
+ * - Saves world, all agents, all chat histories, and all events
43
+ * - Creates target directory if it doesn't exist
44
+ * - Supports migration between storage types
45
+ * - Event history preserved across different storage backends
46
+ */
47
+ import { LLMProvider, createWorld, getWorld, updateWorld, publishMessage, listWorlds, deleteWorld, listAgents, getAgent, updateAgent, deleteAgent, createAgent, clearAgentMemory, listChats, updateChat, exportWorldToMarkdown, exportChatToMarkdown, newChat, restoreChat, deleteChat, getMemory } from '../core/index.js';
48
+ import { createStorage } from '../core/storage/storage-factory.js';
49
+ import { createCategoryLogger } from '../core/logger.js';
50
+ import readline from 'readline';
51
+ import enquirer from 'enquirer';
52
+ import fs from 'fs';
53
+ import path from 'path';
54
+ // Create CLI logger
55
+ const logger = createCategoryLogger('cli');
56
+ // Helper for world-required command validation
57
+ function requireWorldOrError(world, command) {
58
+ if (!world) {
59
+ return {
60
+ success: false,
61
+ message: 'No world selected. World is required for this command.',
62
+ technicalDetails: `Command ${command} requires world context`
63
+ };
64
+ }
65
+ return undefined;
66
+ }
67
+ // Color helpers (matching cli/index.ts styles)
68
+ const red = (text) => `\x1b[31m${text}\x1b[0m`;
69
+ const green = (text) => `\x1b[32m${text}\x1b[0m`;
70
+ const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
71
+ const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
72
+ const gray = (text) => `\x1b[90m${text}\x1b[0m`;
73
+ const boldGreen = (text) => `\x1b[1m\x1b[32m${text}\x1b[0m`;
74
+ const boldYellow = (text) => `\x1b[1m\x1b[33m${text}\x1b[0m`;
75
+ const boldRed = (text) => `\x1b[1m\x1b[31m${text}\x1b[0m`;
76
+ /**
77
+ * Display chat messages in a formatted, readable way
78
+ * Shows sender, timestamp, and content for each message
79
+ * Logic:
80
+ * - HUMAN messages: deduplicate by messageId (they're replicated across all agents)
81
+ * - Agent messages: only show if sender matches agentId (the agent that created it)
82
+ * - System messages: deduplicate by messageId
83
+ */
84
+ export async function displayChatMessages(worldId, chatId) {
85
+ try {
86
+ const messages = await getMemory(worldId, chatId);
87
+ if (!messages || messages.length === 0) {
88
+ console.log(gray('\n No messages in current chat.\n'));
89
+ return;
90
+ }
91
+ // Filter and deduplicate messages
92
+ const humanMessageMap = new Map();
93
+ const agentMessages = [];
94
+ const messagesWithoutId = [];
95
+ for (const msg of messages) {
96
+ const isHumanMessage = msg.sender === 'human' ||
97
+ msg.role === 'user' ||
98
+ (msg.sender || '').toLowerCase() === 'human';
99
+ const isSystemMessage = msg.sender === 'system' || msg.role === 'system';
100
+ if (isHumanMessage) {
101
+ // Deduplicate human messages by messageId (exclude agent response copies with role=user)
102
+ if (msg.messageId && (msg.sender === 'human' || (msg.sender || '').toLowerCase() === 'human')) {
103
+ if (!humanMessageMap.has(msg.messageId)) {
104
+ humanMessageMap.set(msg.messageId, msg);
105
+ }
106
+ }
107
+ else if (!msg.messageId && msg.sender === 'human') {
108
+ messagesWithoutId.push(msg);
109
+ }
110
+ }
111
+ else if (isSystemMessage) {
112
+ // Deduplicate system messages by messageId
113
+ if (msg.messageId) {
114
+ if (!humanMessageMap.has(msg.messageId)) {
115
+ humanMessageMap.set(msg.messageId, msg);
116
+ }
117
+ }
118
+ else {
119
+ messagesWithoutId.push(msg);
120
+ }
121
+ }
122
+ else if (msg.role === 'assistant') {
123
+ // Agent response message: only show messages with role=assistant
124
+ // This filters out copies stored in other agents' memories (which have role=user)
125
+ agentMessages.push(msg);
126
+ }
127
+ }
128
+ // Combine all message types
129
+ const deduplicatedMessages = [
130
+ ...Array.from(humanMessageMap.values()),
131
+ ...agentMessages,
132
+ ...messagesWithoutId
133
+ ];
134
+ // Sort by timestamp
135
+ deduplicatedMessages.sort((a, b) => {
136
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
137
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
138
+ return dateA - dateB;
139
+ });
140
+ console.log(cyan('\n=== Current Chat Messages ===\n'));
141
+ for (const msg of deduplicatedMessages) {
142
+ // Format timestamp if available
143
+ const timestamp = msg.createdAt
144
+ ? new Date(msg.createdAt).toLocaleString('en-US', {
145
+ month: 'short',
146
+ day: 'numeric',
147
+ hour: '2-digit',
148
+ minute: '2-digit',
149
+ second: '2-digit'
150
+ })
151
+ : '';
152
+ // Determine sender display
153
+ let senderDisplay = '';
154
+ if (msg.sender) {
155
+ // Color code by sender type
156
+ const senderLower = msg.sender.toLowerCase();
157
+ if (senderLower === 'human') {
158
+ senderDisplay = boldYellow('HUMAN');
159
+ }
160
+ else if (msg.sender === 'system') {
161
+ senderDisplay = boldRed('system');
162
+ }
163
+ else {
164
+ // Agent message
165
+ senderDisplay = boldGreen(msg.sender);
166
+ }
167
+ }
168
+ else {
169
+ // Fallback to role if no sender
170
+ senderDisplay = gray(msg.role || 'unknown');
171
+ }
172
+ // Display message
173
+ const timestampPart = timestamp ? gray(`[${timestamp}]`) : '';
174
+ console.log(`${timestampPart} ${senderDisplay}: ${msg.content}`);
175
+ }
176
+ console.log(gray(`\n Total: ${deduplicatedMessages.length} message${deduplicatedMessages.length !== 1 ? 's' : ''}\n`));
177
+ }
178
+ catch (err) {
179
+ console.error(red(`Failed to load chat messages: ${err instanceof Error ? err.message : String(err)}`));
180
+ }
181
+ }
182
+ export const CLI_COMMAND_MAP = {
183
+ 'world list': {
184
+ type: 'listWorlds',
185
+ requiresWorld: false,
186
+ description: 'List all worlds with details (ID, name, description, agents count)',
187
+ usage: '/world list',
188
+ parameters: [],
189
+ aliases: ['list worlds', 'lsw', 'list-worlds'],
190
+ category: 'world'
191
+ },
192
+ 'world show': {
193
+ type: 'showWorld',
194
+ requiresWorld: false,
195
+ description: 'Show details for a specific world',
196
+ usage: '/world show <name>',
197
+ parameters: [
198
+ { name: 'name', required: true, description: 'World name or ID', type: 'string' }
199
+ ],
200
+ aliases: ['show world', 'show-world'],
201
+ category: 'world'
202
+ },
203
+ 'world create': {
204
+ type: 'createWorld',
205
+ requiresWorld: false,
206
+ description: 'Create a new world',
207
+ usage: '/world create [name] [description] [turnLimit]',
208
+ parameters: [
209
+ { name: 'name', required: false, description: 'World name', type: 'string' },
210
+ { name: 'description', required: false, description: 'World description', type: 'string' },
211
+ { name: 'turnLimit', required: false, description: 'Turn limit for the world', type: 'number' }
212
+ ],
213
+ aliases: ['create world', 'new', 'create-world'],
214
+ category: 'world'
215
+ },
216
+ 'world update': {
217
+ type: 'updateWorld',
218
+ requiresWorld: false,
219
+ description: 'Update world properties interactively',
220
+ usage: '/world update <name>',
221
+ parameters: [
222
+ { name: 'name', required: true, description: 'World name or ID', type: 'string' }
223
+ ],
224
+ aliases: ['update world', 'update-world'],
225
+ category: 'world'
226
+ },
227
+ 'world delete': {
228
+ type: 'deleteWorld',
229
+ requiresWorld: false,
230
+ description: 'Delete a world after confirmation',
231
+ usage: '/world delete <name>',
232
+ parameters: [
233
+ { name: 'name', required: true, description: 'World name or ID', type: 'string' }
234
+ ],
235
+ aliases: ['delete world', 'delete-world'],
236
+ category: 'world'
237
+ },
238
+ 'world select': {
239
+ type: 'selectWorld',
240
+ requiresWorld: false,
241
+ description: 'Show world selection menu to pick a world',
242
+ usage: '/world select',
243
+ parameters: [],
244
+ aliases: ['select world', 'select', 'sel'],
245
+ category: 'world'
246
+ },
247
+ 'world export': {
248
+ type: 'exportWorld',
249
+ requiresWorld: true,
250
+ description: 'Export the current world and agents to a markdown file',
251
+ usage: '/world export [file]',
252
+ parameters: [
253
+ { name: 'file', required: false, description: 'Output file path (defaults to [world]-timestamp.md)', type: 'string' }
254
+ ],
255
+ aliases: ['export world', 'export'],
256
+ category: 'world'
257
+ },
258
+ 'world save': {
259
+ type: 'saveWorld',
260
+ requiresWorld: true,
261
+ description: 'Save world data to File Storage or SQL Storage with folder selection (overwrites existing data with confirmation)',
262
+ usage: '/world save',
263
+ parameters: [],
264
+ aliases: ['save world', 'save'],
265
+ category: 'world'
266
+ },
267
+ 'agent list': {
268
+ type: 'listAgents',
269
+ requiresWorld: true,
270
+ description: 'List all agents in the current world with details',
271
+ usage: '/agent list',
272
+ parameters: [],
273
+ aliases: ['list agents', 'lsa', 'list-agents'],
274
+ category: 'agent'
275
+ },
276
+ 'agent show': {
277
+ type: 'showAgent',
278
+ requiresWorld: true,
279
+ description: 'Show agent details including configuration and memory statistics',
280
+ usage: '/agent show <name>',
281
+ parameters: [
282
+ { name: 'name', required: true, description: 'Agent name', type: 'string' }
283
+ ],
284
+ aliases: ['show agent', 'show-agent'],
285
+ category: 'agent'
286
+ },
287
+ 'agent create': {
288
+ type: 'createAgent',
289
+ requiresWorld: true,
290
+ description: 'Create a new agent',
291
+ usage: '/agent create [name] [prompt]',
292
+ parameters: [
293
+ { name: 'name', required: false, description: 'Agent name', type: 'string' },
294
+ { name: 'prompt', required: false, description: 'Agent system prompt', type: 'string' }
295
+ ],
296
+ aliases: ['create agent', 'agent new', 'add agent', 'agent add', 'add', 'add-agent'],
297
+ category: 'agent'
298
+ },
299
+ 'agent update': {
300
+ type: 'updateAgent',
301
+ requiresWorld: true,
302
+ description: 'Update agent properties interactively',
303
+ usage: '/agent update <name>',
304
+ parameters: [
305
+ { name: 'name', required: true, description: 'Agent name', type: 'string' }
306
+ ],
307
+ aliases: ['update agent', 'update-agent'],
308
+ category: 'agent'
309
+ },
310
+ 'agent delete': {
311
+ type: 'deleteAgent',
312
+ requiresWorld: true,
313
+ description: 'Delete an agent after confirmation',
314
+ usage: '/agent delete <name>',
315
+ parameters: [
316
+ { name: 'name', required: true, description: 'Agent name', type: 'string' }
317
+ ],
318
+ aliases: ['delete agent', 'delete-agent'],
319
+ category: 'agent'
320
+ },
321
+ 'agent clear': {
322
+ type: 'clearAgentMemory',
323
+ requiresWorld: true,
324
+ description: 'Clear agent memory or all agents',
325
+ usage: '/agent clear <agentName|all>',
326
+ parameters: [
327
+ { name: 'agentName', required: true, description: 'Agent name or "all" for all agents', type: 'string' }
328
+ ],
329
+ aliases: ['clear'],
330
+ category: 'agent'
331
+ },
332
+ 'chat list': {
333
+ type: 'listChats',
334
+ requiresWorld: true,
335
+ description: 'List chat history for the current world',
336
+ usage: '/chat list [--active]',
337
+ parameters: [
338
+ { name: 'filter', required: false, description: 'Optional filter (--active for current chat only)', type: 'string' }
339
+ ],
340
+ aliases: ['list chats', 'list-chats'],
341
+ category: 'chat'
342
+ },
343
+ 'chat create': {
344
+ type: 'createChat',
345
+ requiresWorld: true,
346
+ description: 'Create a new chat history entry and make it current',
347
+ usage: '/chat create',
348
+ parameters: [],
349
+ aliases: ['create chat', 'chat new', 'new chat', 'new-chat'],
350
+ category: 'chat'
351
+ },
352
+ 'chat select': {
353
+ type: 'selectChat',
354
+ requiresWorld: true,
355
+ description: 'Show chat selection menu and display messages from selected chat',
356
+ usage: '/chat select',
357
+ parameters: [],
358
+ aliases: ['select chat', 'select-chat'],
359
+ category: 'chat'
360
+ },
361
+ 'chat switch': {
362
+ type: 'loadChat',
363
+ requiresWorld: true,
364
+ description: 'Load and restore state from a chat history entry',
365
+ usage: '/chat switch <chatId>',
366
+ parameters: [
367
+ { name: 'chatId', required: true, description: 'Chat ID to load', type: 'string' }
368
+ ],
369
+ aliases: ['switch chat', 'load chat', 'load-chat'],
370
+ category: 'chat'
371
+ },
372
+ 'chat delete': {
373
+ type: 'deleteChat',
374
+ requiresWorld: true,
375
+ description: 'Delete a chat history entry after confirmation',
376
+ usage: '/chat delete <chatId>',
377
+ parameters: [
378
+ { name: 'chatId', required: true, description: 'Chat ID to delete', type: 'string' }
379
+ ],
380
+ aliases: ['delete chat', 'delete-chat'],
381
+ category: 'chat'
382
+ },
383
+ 'chat rename': {
384
+ type: 'renameChat',
385
+ requiresWorld: true,
386
+ description: 'Rename a chat history entry and optionally update its description',
387
+ usage: '/chat rename <chatId> <name> [description]',
388
+ parameters: [
389
+ { name: 'chatId', required: true, description: 'Chat ID to rename', type: 'string' },
390
+ { name: 'name', required: true, description: 'New chat name', type: 'string' },
391
+ { name: 'description', required: false, description: 'New chat description', type: 'string' }
392
+ ],
393
+ aliases: ['rename chat', 'rename-chat'],
394
+ category: 'chat'
395
+ },
396
+ 'chat export': {
397
+ type: 'exportChat',
398
+ requiresWorld: true,
399
+ description: 'Export a chat history to markdown (defaults to current chat)',
400
+ usage: '/chat export [chatId] [file]',
401
+ parameters: [
402
+ { name: 'chatId', required: false, description: 'Chat ID to export (defaults to current chat)', type: 'string' },
403
+ { name: 'file', required: false, description: 'Output file path', type: 'string' }
404
+ ],
405
+ aliases: ['export chat', 'export-chat'],
406
+ category: 'chat'
407
+ },
408
+ 'help': {
409
+ type: 'help',
410
+ requiresWorld: false,
411
+ description: 'Show available commands or category-specific help',
412
+ usage: '/help [command|category]',
413
+ parameters: [
414
+ { name: 'command', required: false, description: 'Command or category to display', type: 'string' }
415
+ ],
416
+ category: 'system'
417
+ },
418
+ 'quit': {
419
+ type: 'quit',
420
+ requiresWorld: false,
421
+ description: 'Exit the CLI',
422
+ usage: '/quit',
423
+ parameters: [],
424
+ category: 'system'
425
+ },
426
+ 'exit': {
427
+ type: 'exit',
428
+ requiresWorld: false,
429
+ description: 'Exit the CLI',
430
+ usage: '/exit',
431
+ parameters: [],
432
+ category: 'system'
433
+ }
434
+ };
435
+ export const CLI_COMMAND_ALIASES = Object.entries(CLI_COMMAND_MAP).reduce((aliases, [key, command]) => {
436
+ if (command.aliases) {
437
+ for (const rawAlias of command.aliases) {
438
+ const alias = rawAlias.replace(/^\//, '').toLowerCase();
439
+ if (!aliases[alias]) {
440
+ aliases[alias] = key;
441
+ }
442
+ }
443
+ }
444
+ return aliases;
445
+ }, {});
446
+ const CATEGORY_LABELS = Object.freeze({
447
+ world: 'World Management',
448
+ agent: 'Agent Management',
449
+ chat: 'Chat Management',
450
+ system: 'System Commands'
451
+ });
452
+ const CATEGORY_ORDER = ['world', 'agent', 'chat', 'system'];
453
+ // Command parsing and help generation
454
+ export function parseCLICommand(input) {
455
+ const trimmed = input.trim();
456
+ if (!trimmed.startsWith('/')) {
457
+ return {
458
+ command: '',
459
+ args: [],
460
+ commandType: '',
461
+ isValid: false,
462
+ error: 'Commands must start with /'
463
+ };
464
+ }
465
+ const parts = trimmed.slice(1).split(/\s+/).filter(part => part.length > 0);
466
+ if (parts.length === 0) {
467
+ return {
468
+ command: '',
469
+ args: [],
470
+ commandType: '',
471
+ isValid: false,
472
+ error: 'Empty command'
473
+ };
474
+ }
475
+ const tokens = parts.map(part => part.toLowerCase());
476
+ let resolvedCommand = null;
477
+ let consumedTokens = 0;
478
+ if (tokens.length >= 2) {
479
+ const twoWordCandidate = `${tokens[0]} ${tokens[1]}`;
480
+ if (CLI_COMMAND_MAP[twoWordCandidate]) {
481
+ resolvedCommand = twoWordCandidate;
482
+ consumedTokens = 2;
483
+ }
484
+ else if (CLI_COMMAND_ALIASES[twoWordCandidate]) {
485
+ resolvedCommand = CLI_COMMAND_ALIASES[twoWordCandidate];
486
+ consumedTokens = 2;
487
+ }
488
+ }
489
+ if (!resolvedCommand) {
490
+ const singleWordCandidate = tokens[0];
491
+ if (CLI_COMMAND_MAP[singleWordCandidate]) {
492
+ resolvedCommand = singleWordCandidate;
493
+ consumedTokens = 1;
494
+ }
495
+ else if (CLI_COMMAND_ALIASES[singleWordCandidate]) {
496
+ resolvedCommand = CLI_COMMAND_ALIASES[singleWordCandidate];
497
+ consumedTokens = 1;
498
+ }
499
+ }
500
+ if (!resolvedCommand) {
501
+ const availableCommands = Object.keys(CLI_COMMAND_MAP).sort().map(cmd => `/${cmd}`).join(', ');
502
+ const attempted = tokens.slice(0, 2).join(' ');
503
+ return {
504
+ command: attempted,
505
+ args: parts.slice(1),
506
+ commandType: '',
507
+ isValid: false,
508
+ error: `Unknown command: ${attempted}. Available commands: ${availableCommands}`
509
+ };
510
+ }
511
+ const args = parts.slice(consumedTokens);
512
+ return {
513
+ command: resolvedCommand,
514
+ args,
515
+ commandType: CLI_COMMAND_MAP[resolvedCommand].type,
516
+ isValid: true
517
+ };
518
+ }
519
+ export function generateHelpMessage(target) {
520
+ const formatAliases = (definition) => {
521
+ if (!definition.aliases || definition.aliases.length === 0) {
522
+ return '';
523
+ }
524
+ const aliasList = definition.aliases.map(alias => `/${alias}`).join(', ');
525
+ return `Aliases: ${aliasList}\n`;
526
+ };
527
+ const formatParameters = (definition) => {
528
+ if (!definition.parameters.length) {
529
+ return '';
530
+ }
531
+ let text = '\nParameters:\n';
532
+ for (const param of definition.parameters) {
533
+ const required = param.required ? 'required' : 'optional';
534
+ const options = param.options ? ` (options: ${param.options.join(', ')})` : '';
535
+ text += ` ${param.name} (${param.type}, ${required}): ${param.description}${options}\n`;
536
+ }
537
+ return text;
538
+ };
539
+ const formatCategorySection = (category) => {
540
+ const commands = Object.entries(CLI_COMMAND_MAP)
541
+ .filter(([, definition]) => definition.category === category)
542
+ .sort((a, b) => a[1].usage.localeCompare(b[1].usage));
543
+ if (commands.length === 0) {
544
+ return '';
545
+ }
546
+ let section = `${CATEGORY_LABELS[category]}:\n`;
547
+ for (const [, definition] of commands) {
548
+ const aliasLabel = definition.aliases && definition.aliases.length
549
+ ? ` (aliases: ${definition.aliases.map(alias => `/${alias}`).join(', ')})`
550
+ : '';
551
+ section += ` ${definition.usage.padEnd(28)} - ${definition.description}${aliasLabel}\n`;
552
+ }
553
+ return `${section}\n`;
554
+ };
555
+ const commandKeyFromTarget = (candidate) => {
556
+ if (CLI_COMMAND_MAP[candidate]) {
557
+ return candidate;
558
+ }
559
+ if (CLI_COMMAND_ALIASES[candidate]) {
560
+ return CLI_COMMAND_ALIASES[candidate];
561
+ }
562
+ return null;
563
+ };
564
+ if (target) {
565
+ const normalized = target.toLowerCase();
566
+ const category = CATEGORY_ORDER.find(cat => cat === normalized);
567
+ if (category) {
568
+ const section = formatCategorySection(category);
569
+ return `\n${section}Use /help <command> for detailed information about a specific command.\n`;
570
+ }
571
+ const resolvedKey = commandKeyFromTarget(normalized);
572
+ if (resolvedKey) {
573
+ const definition = CLI_COMMAND_MAP[resolvedKey];
574
+ let help = `\n${definition.usage}\n`;
575
+ help += `Description: ${definition.description}\n`;
576
+ help += `Category: ${CATEGORY_LABELS[definition.category]}\n`;
577
+ help += formatAliases(definition);
578
+ help += formatParameters(definition);
579
+ return help;
580
+ }
581
+ return `\nUnknown command or category: ${target}. Try /help, /help world, /help agent, or /help chat.\n`;
582
+ }
583
+ let help = '\nCommand Guide\n';
584
+ help += 'Commands are grouped by domain: /world …, /agent …, /chat …\n';
585
+ help += 'Examples: /world list | /agent create Ava | /chat list --active\n\n';
586
+ for (const category of CATEGORY_ORDER) {
587
+ const section = formatCategorySection(category);
588
+ if (section) {
589
+ help += section;
590
+ }
591
+ }
592
+ help += 'Use /help <command> for detailed information or /help <category> to filter by topic.\n';
593
+ return help;
594
+ }
595
+ // Export world to markdown file (CLI wrapper)
596
+ async function exportWorldToMarkdownFile(worldName, outputPath) {
597
+ try {
598
+ // Use the core function to generate markdown
599
+ const markdown = await exportWorldToMarkdown(worldName);
600
+ // Generate timestamp for default filename (YYYY-MM-DD_HH-MM-SS)
601
+ const now = new Date();
602
+ const datePart = now.toISOString().slice(0, 10); // YYYY-MM-DD
603
+ const timePart = now
604
+ .toTimeString()
605
+ .slice(0, 8)
606
+ .replace(/:/g, '-'); // HH-MM-SS
607
+ const timestamp = `${datePart}_${timePart}`;
608
+ // Determine output file path
609
+ let filePath;
610
+ if (outputPath) {
611
+ filePath = path.resolve(outputPath);
612
+ }
613
+ else {
614
+ filePath = path.resolve(process.cwd(), `${worldName}-${timestamp}.md`);
615
+ }
616
+ // Write file
617
+ await fs.promises.writeFile(filePath, markdown, 'utf8');
618
+ // Get agent count for response data
619
+ const worldData = await getWorld(worldName);
620
+ const world = await getWorld(worldData.id);
621
+ if (!world)
622
+ throw new Error(`World ${worldName} not found`);
623
+ const agents = await listAgents(worldData.id);
624
+ return {
625
+ success: true,
626
+ message: `World '${worldName}' exported successfully to: ${filePath}`,
627
+ data: {
628
+ worldName,
629
+ filePath,
630
+ agentCount: agents.length,
631
+ fileSize: markdown.length
632
+ }
633
+ };
634
+ }
635
+ catch (error) {
636
+ return {
637
+ success: false,
638
+ message: 'Failed to export world',
639
+ error: error instanceof Error ? error.message : String(error)
640
+ };
641
+ }
642
+ }
643
+ async function exportChatToMarkdownFile(worldId, worldName, chatId, outputPath) {
644
+ try {
645
+ const markdown = await exportChatToMarkdown(worldId, chatId);
646
+ const now = new Date();
647
+ const datePart = now.toISOString().slice(0, 10);
648
+ const timePart = now.toTimeString().slice(0, 8).replace(/:/g, '-');
649
+ const timestamp = `${datePart}_${timePart}`;
650
+ const sanitizedWorld = worldName.replace(/\s+/g, '-');
651
+ const filePath = outputPath
652
+ ? path.resolve(outputPath)
653
+ : path.resolve(process.cwd(), `${sanitizedWorld}-${chatId}-${timestamp}.md`);
654
+ await fs.promises.writeFile(filePath, markdown, 'utf8');
655
+ return {
656
+ success: true,
657
+ message: `Chat '${chatId}' exported successfully to: ${filePath}`,
658
+ data: {
659
+ worldId,
660
+ chatId,
661
+ worldName,
662
+ filePath,
663
+ fileSize: markdown.length
664
+ }
665
+ };
666
+ }
667
+ catch (error) {
668
+ return {
669
+ success: false,
670
+ message: 'Failed to export chat',
671
+ error: error instanceof Error ? error.message : String(error)
672
+ };
673
+ }
674
+ }
675
+ // Save world to different storage (File or SQLite)
676
+ async function saveWorldToStorage(world) {
677
+ try {
678
+ // This function returns data to trigger interactive selection in CLI
679
+ // The actual save will be handled by the CLI after user selection
680
+ return {
681
+ success: true,
682
+ message: 'Opening storage selection...',
683
+ data: {
684
+ saveWorld: true,
685
+ worldId: world.id,
686
+ worldName: world.name
687
+ }
688
+ };
689
+ }
690
+ catch (error) {
691
+ return {
692
+ success: false,
693
+ message: 'Failed to initiate world save',
694
+ error: error instanceof Error ? error.message : String(error)
695
+ };
696
+ }
697
+ }
698
+ // Check if target storage location exists
699
+ export async function checkTargetExists(targetPath, storageType, worldId) {
700
+ try {
701
+ if (storageType === 'sqlite') {
702
+ const dbPath = path.join(targetPath, 'database.db');
703
+ if (fs.existsSync(dbPath)) {
704
+ return {
705
+ exists: true,
706
+ message: `SQLite database already exists at ${dbPath}`
707
+ };
708
+ }
709
+ }
710
+ else {
711
+ // For file storage, check if world folder exists
712
+ const worldPath = path.join(targetPath, worldId);
713
+ if (fs.existsSync(worldPath)) {
714
+ return {
715
+ exists: true,
716
+ message: `World folder already exists at ${worldPath}`
717
+ };
718
+ }
719
+ }
720
+ return { exists: false, message: '' };
721
+ }
722
+ catch (error) {
723
+ return { exists: false, message: '' };
724
+ }
725
+ }
726
+ // Delete existing data at target location
727
+ export async function deleteExistingData(targetPath, storageType, worldId) {
728
+ try {
729
+ if (storageType === 'sqlite') {
730
+ const dbPath = path.join(targetPath, 'database.db');
731
+ if (fs.existsSync(dbPath)) {
732
+ // Delete the database file and related files
733
+ fs.unlinkSync(dbPath);
734
+ // Also delete WAL and SHM files if they exist
735
+ const walPath = `${dbPath}-wal`;
736
+ const shmPath = `${dbPath}-shm`;
737
+ if (fs.existsSync(walPath))
738
+ fs.unlinkSync(walPath);
739
+ if (fs.existsSync(shmPath))
740
+ fs.unlinkSync(shmPath);
741
+ }
742
+ }
743
+ else {
744
+ // For file storage, delete the world folder
745
+ const worldPath = path.join(targetPath, worldId);
746
+ if (fs.existsSync(worldPath)) {
747
+ fs.rmSync(worldPath, { recursive: true, force: true });
748
+ }
749
+ }
750
+ return { success: true };
751
+ }
752
+ catch (error) {
753
+ return {
754
+ success: false,
755
+ error: error instanceof Error ? error.message : String(error)
756
+ };
757
+ }
758
+ }
759
+ // Actual save implementation called from CLI after user selection
760
+ export async function performWorldSave(world, storageType, targetPath) {
761
+ try {
762
+ // Ensure directory exists
763
+ if (!fs.existsSync(targetPath)) {
764
+ fs.mkdirSync(targetPath, { recursive: true });
765
+ }
766
+ // Create storage configuration
767
+ const config = {
768
+ type: storageType,
769
+ rootPath: targetPath,
770
+ sqlite: storageType === 'sqlite'
771
+ ? {
772
+ database: path.join(targetPath, 'database.db'),
773
+ enableWAL: true,
774
+ busyTimeout: 30000,
775
+ cacheSize: -64000,
776
+ enableForeignKeys: true
777
+ }
778
+ : undefined
779
+ };
780
+ // Create storage instance
781
+ const storage = await createStorage(config);
782
+ // Save world data
783
+ await storage.saveWorld(world);
784
+ // Save all agents
785
+ const agents = await listAgents(world.id);
786
+ for (const agent of agents) {
787
+ await storage.saveAgent(world.id, agent);
788
+ }
789
+ // Save all chats
790
+ const chats = await listChats(world.id);
791
+ for (const chat of chats) {
792
+ await storage.saveChatData(world.id, chat);
793
+ }
794
+ // Save all events if eventStorage is available on both source and target
795
+ let eventCount = 0;
796
+ if (world.eventStorage && storage.eventStorage) {
797
+ try {
798
+ const sourceEventStorage = world.eventStorage;
799
+ const targetEventStorage = storage.eventStorage;
800
+ // Get events for world-level context (chatId = null)
801
+ const worldEvents = await sourceEventStorage.getEventsByWorldAndChat(world.id, null);
802
+ if (worldEvents && worldEvents.length > 0) {
803
+ await targetEventStorage.saveEvents(worldEvents);
804
+ eventCount = worldEvents.length;
805
+ }
806
+ // Get events for each specific chat
807
+ for (const chat of chats) {
808
+ const chatEvents = await sourceEventStorage.getEventsByWorldAndChat(world.id, chat.id);
809
+ if (chatEvents && chatEvents.length > 0) {
810
+ await targetEventStorage.saveEvents(chatEvents);
811
+ eventCount += chatEvents.length;
812
+ }
813
+ }
814
+ }
815
+ catch (error) {
816
+ // Log but don't fail the save if events can't be copied
817
+ console.warn(`Warning: Failed to copy events: ${error instanceof Error ? error.message : String(error)}`);
818
+ }
819
+ }
820
+ return {
821
+ success: true,
822
+ message: `World '${world.name}' saved successfully to ${storageType} storage at: ${targetPath}`,
823
+ data: {
824
+ worldName: world.name,
825
+ worldId: world.id,
826
+ storageType,
827
+ path: targetPath,
828
+ agentCount: agents.length,
829
+ chatCount: chats.length,
830
+ eventCount
831
+ }
832
+ };
833
+ }
834
+ catch (error) {
835
+ return {
836
+ success: false,
837
+ message: 'Failed to save world to storage',
838
+ error: error instanceof Error ? error.message : String(error)
839
+ };
840
+ }
841
+ }
842
+ // CLI Command Processor
843
+ export async function processCLICommand(input, context, promptFn) {
844
+ try {
845
+ const { command, args, commandType, isValid, error } = parseCLICommand(input);
846
+ if (!isValid) {
847
+ return {
848
+ success: false,
849
+ message: error || 'Invalid command',
850
+ technicalDetails: `Failed to parse: ${input}`
851
+ };
852
+ }
853
+ if (command === 'help') {
854
+ const helpCommand = args[0];
855
+ return {
856
+ success: true,
857
+ message: generateHelpMessage(helpCommand),
858
+ data: { command: helpCommand }
859
+ };
860
+ }
861
+ const commandInfo = CLI_COMMAND_MAP[command];
862
+ // Check world requirement
863
+ if (commandInfo.requiresWorld && !context.currentWorldName) {
864
+ return {
865
+ success: false,
866
+ message: 'No world selected. World is required for this command.',
867
+ technicalDetails: `Command ${command} requires world context`
868
+ };
869
+ }
870
+ // Collect parameters from command arguments
871
+ const collectedParams = {};
872
+ for (let i = 0; i < commandInfo.parameters.length; i++) {
873
+ const param = commandInfo.parameters[i];
874
+ let value = args[i];
875
+ if (!value && param.required) {
876
+ return {
877
+ success: false,
878
+ message: `Missing required parameter: ${param.name}`,
879
+ technicalDetails: `Usage: ${commandInfo.usage}`
880
+ };
881
+ }
882
+ if (value) {
883
+ // Type conversion
884
+ if (param.type === 'number') {
885
+ const numValue = parseInt(value);
886
+ if (isNaN(numValue)) {
887
+ return {
888
+ success: false,
889
+ message: `${param.name} must be a number`,
890
+ technicalDetails: `Invalid number: ${value}`
891
+ };
892
+ }
893
+ collectedParams[param.name] = numValue;
894
+ }
895
+ else if (param.type === 'boolean') {
896
+ collectedParams[param.name] = value.toLowerCase() === 'true';
897
+ }
898
+ else {
899
+ collectedParams[param.name] = value;
900
+ }
901
+ // Validate options
902
+ if (param.options && !param.options.includes(collectedParams[param.name])) {
903
+ return {
904
+ success: false,
905
+ message: `Invalid ${param.name}. Valid options: ${param.options.join(', ')}`,
906
+ technicalDetails: `Invalid option: ${value}`
907
+ };
908
+ }
909
+ }
910
+ }
911
+ // Use already loaded world from context
912
+ let world = null;
913
+ if (commandInfo.requiresWorld) {
914
+ if (context.currentWorld) {
915
+ world = context.currentWorld;
916
+ }
917
+ else {
918
+ return {
919
+ success: false,
920
+ message: 'No world available',
921
+ technicalDetails: 'Command requires world context but no world is loaded'
922
+ };
923
+ }
924
+ }
925
+ // Execute command using core functions
926
+ let cliResponse;
927
+ switch (commandInfo.type) {
928
+ case 'createWorld': {
929
+ const shouldPrompt = collectedParams.name === undefined;
930
+ if (shouldPrompt) {
931
+ try {
932
+ const prompts = [
933
+ {
934
+ type: 'input',
935
+ name: 'name',
936
+ message: 'World name:',
937
+ required: true
938
+ },
939
+ {
940
+ type: 'input',
941
+ name: 'description',
942
+ message: 'World description:'
943
+ },
944
+ {
945
+ type: 'numeral',
946
+ name: 'turnLimit',
947
+ message: 'Turn limit:',
948
+ initial: 5
949
+ }
950
+ ];
951
+ const answers = await enquirer.prompt(prompts);
952
+ const newWorld = await createWorld({
953
+ name: answers.name,
954
+ description: answers.description || `A world named ${answers.name}`,
955
+ turnLimit: answers.turnLimit
956
+ });
957
+ cliResponse = {
958
+ success: true,
959
+ message: `World '${answers.name}' created successfully`,
960
+ data: newWorld,
961
+ needsWorldRefresh: true
962
+ };
963
+ }
964
+ catch (error) {
965
+ cliResponse = {
966
+ success: false,
967
+ message: 'Failed to create world',
968
+ error: error instanceof Error ? error.message : String(error)
969
+ };
970
+ }
971
+ }
972
+ else {
973
+ try {
974
+ const newWorld = await createWorld({
975
+ name: collectedParams.name,
976
+ description: collectedParams.description || `A world named ${collectedParams.name}`,
977
+ turnLimit: collectedParams.turnLimit
978
+ });
979
+ cliResponse = {
980
+ success: true,
981
+ message: `World '${collectedParams.name}' created successfully`,
982
+ data: newWorld,
983
+ needsWorldRefresh: true
984
+ };
985
+ }
986
+ catch (error) {
987
+ cliResponse = {
988
+ success: false,
989
+ message: 'Failed to create world',
990
+ error: error instanceof Error ? error.message : String(error)
991
+ };
992
+ }
993
+ }
994
+ break;
995
+ }
996
+ case 'selectWorld':
997
+ cliResponse = {
998
+ success: true,
999
+ message: 'Opening world selection...',
1000
+ data: { selectWorld: true }
1001
+ };
1002
+ break;
1003
+ case 'createAgent': {
1004
+ const worldError = requireWorldOrError(world, command);
1005
+ if (worldError)
1006
+ return worldError;
1007
+ const shouldPrompt = collectedParams.name === undefined;
1008
+ if (shouldPrompt) {
1009
+ try {
1010
+ const prompts = [
1011
+ {
1012
+ type: 'input',
1013
+ name: 'name',
1014
+ message: 'Agent name:',
1015
+ required: true
1016
+ },
1017
+ {
1018
+ type: 'select',
1019
+ name: 'provider',
1020
+ message: 'LLM Provider:',
1021
+ choices: Object.values(LLMProvider),
1022
+ initial: 'openai'
1023
+ },
1024
+ {
1025
+ type: 'input',
1026
+ name: 'model',
1027
+ message: 'Model:',
1028
+ initial: 'gpt-4'
1029
+ },
1030
+ {
1031
+ type: 'input',
1032
+ name: 'systemPrompt',
1033
+ message: 'System prompt (or press Enter for default):'
1034
+ },
1035
+ {
1036
+ type: 'numeral',
1037
+ name: 'temperature',
1038
+ message: 'Temperature (0.0-2.0):',
1039
+ initial: 0.7
1040
+ },
1041
+ {
1042
+ type: 'numeral',
1043
+ name: 'maxTokens',
1044
+ message: 'Max tokens:',
1045
+ initial: 4096
1046
+ }
1047
+ ];
1048
+ const answers = await enquirer.prompt(prompts);
1049
+ const agent = await createAgent(world.id, {
1050
+ name: answers.name,
1051
+ type: 'conversational',
1052
+ provider: answers.provider,
1053
+ model: answers.model,
1054
+ systemPrompt: answers.systemPrompt || `You are ${answers.name}, an agent in the ${world.name} world.`,
1055
+ temperature: answers.temperature,
1056
+ maxTokens: answers.maxTokens
1057
+ });
1058
+ cliResponse = {
1059
+ success: true,
1060
+ message: `Agent '${answers.name}' created successfully`,
1061
+ data: agent,
1062
+ needsWorldRefresh: true
1063
+ };
1064
+ }
1065
+ catch (error) {
1066
+ cliResponse = {
1067
+ success: false,
1068
+ message: 'Failed to create agent',
1069
+ error: error instanceof Error ? error.message : String(error)
1070
+ };
1071
+ }
1072
+ }
1073
+ else {
1074
+ try {
1075
+ const agent = await createAgent(world.id, {
1076
+ name: collectedParams.name,
1077
+ type: 'conversational',
1078
+ provider: LLMProvider.OPENAI,
1079
+ model: 'gpt-4',
1080
+ systemPrompt: collectedParams.prompt || `You are ${collectedParams.name}, an agent in the ${world.name} world.`
1081
+ });
1082
+ cliResponse = {
1083
+ success: true,
1084
+ message: `Agent '${collectedParams.name}' created successfully`,
1085
+ data: agent,
1086
+ needsWorldRefresh: true
1087
+ };
1088
+ }
1089
+ catch (error) {
1090
+ cliResponse = {
1091
+ success: false,
1092
+ message: 'Failed to create agent',
1093
+ error: error instanceof Error ? error.message : String(error)
1094
+ };
1095
+ }
1096
+ }
1097
+ break;
1098
+ }
1099
+ case 'clearAgentMemory':
1100
+ {
1101
+ const worldError = requireWorldOrError(world, command);
1102
+ if (worldError)
1103
+ return worldError;
1104
+ }
1105
+ logger.debug('clearAgentMemory command started', {
1106
+ agentName: collectedParams.agentName,
1107
+ worldName: world.name,
1108
+ worldId: world.id,
1109
+ agentsInWorld: Array.from(world.agents.keys())
1110
+ });
1111
+ // Handle /clear all to clear all agents' memory
1112
+ if (collectedParams.agentName.toLowerCase() === 'all') {
1113
+ const clearedAgents = [];
1114
+ for (const [agentName] of world.agents) {
1115
+ logger.debug('Clearing memory for agent', { agentName });
1116
+ await clearAgentMemory(world.id, agentName);
1117
+ clearedAgents.push(agentName);
1118
+ }
1119
+ cliResponse = {
1120
+ success: true,
1121
+ message: `Memory cleared for all agents: ${clearedAgents.join(', ')}`,
1122
+ data: { clearedAgents },
1123
+ needsWorldRefresh: true
1124
+ };
1125
+ break;
1126
+ }
1127
+ // Handle single agent clear
1128
+ logger.debug('Looking for agent in world.agents Map', {
1129
+ searchName: collectedParams.agentName,
1130
+ availableAgents: Array.from(world.agents.keys()),
1131
+ agentExists: world.agents.has(collectedParams.agentName)
1132
+ });
1133
+ const agentForClear = world.agents.get(collectedParams.agentName);
1134
+ if (!agentForClear) {
1135
+ logger.debug('Agent not found in world.agents Map');
1136
+ cliResponse = { success: false, message: `Agent '${collectedParams.agentName}' not found`, data: null };
1137
+ break;
1138
+ }
1139
+ logger.debug('Found agent, calling clearAgentMemory', {
1140
+ agentName: agentForClear.name,
1141
+ agentId: agentForClear.id,
1142
+ memoryCount: agentForClear.memory?.length || 0
1143
+ });
1144
+ try {
1145
+ const result = await clearAgentMemory(world.id, collectedParams.agentName);
1146
+ logger.debug('clearAgentMemory result', {
1147
+ success: !!result,
1148
+ resultAgentId: result?.id,
1149
+ resultMemoryCount: result?.memory?.length || 0
1150
+ });
1151
+ cliResponse = {
1152
+ success: true,
1153
+ message: `Agent '${collectedParams.agentName}' memory cleared successfully`,
1154
+ data: null,
1155
+ needsWorldRefresh: true
1156
+ };
1157
+ }
1158
+ catch (error) {
1159
+ logger.error('clearAgentMemory error', { agentName: collectedParams.agentName, error: error instanceof Error ? error.message : error });
1160
+ cliResponse = {
1161
+ success: false,
1162
+ message: `Failed to clear agent memory: ${error instanceof Error ? error.message : error}`,
1163
+ data: null
1164
+ };
1165
+ }
1166
+ break;
1167
+ // New World CRUD commands
1168
+ case 'listWorlds':
1169
+ try {
1170
+ const worlds = await listWorlds();
1171
+ if (worlds.length === 0) {
1172
+ cliResponse = {
1173
+ success: true,
1174
+ message: 'No worlds found.',
1175
+ data: { worlds: [] }
1176
+ };
1177
+ }
1178
+ else {
1179
+ let output = '\nAvailable Worlds:\n';
1180
+ worlds.forEach((worldInfo) => {
1181
+ output += ` ID: ${worldInfo.id}\n`;
1182
+ output += ` Name: ${worldInfo.name}\n`;
1183
+ output += ` Description: ${worldInfo.description || 'No description'}\n`;
1184
+ output += ` Turn Limit: ${worldInfo.turnLimit}\n`;
1185
+ output += ` Agents: ${worldInfo.totalAgents}\n`;
1186
+ output += ` ---\n`;
1187
+ });
1188
+ cliResponse = {
1189
+ success: true,
1190
+ message: output,
1191
+ data: { worlds }
1192
+ };
1193
+ }
1194
+ }
1195
+ catch (error) {
1196
+ cliResponse = {
1197
+ success: false,
1198
+ message: 'Failed to list worlds',
1199
+ error: error instanceof Error ? error.message : String(error)
1200
+ };
1201
+ }
1202
+ break;
1203
+ case 'showWorld':
1204
+ try {
1205
+ const worldData = await getWorld(collectedParams.name);
1206
+ if (!worldData) {
1207
+ cliResponse = {
1208
+ success: false,
1209
+ message: `World '${collectedParams.name}' not found`
1210
+ };
1211
+ }
1212
+ else {
1213
+ // Get agent count
1214
+ const world = await getWorld(worldData.id);
1215
+ if (!world)
1216
+ throw new Error(`World ${collectedParams.name} not found`);
1217
+ const agents = await listAgents(worldData.id);
1218
+ let output = `\nWorld Details:\n`;
1219
+ output += ` ID: ${worldData.id}\n`;
1220
+ output += ` Name: ${worldData.name}\n`;
1221
+ output += ` Description: ${worldData.description || 'No description'}\n`;
1222
+ output += ` Turn Limit: ${worldData.turnLimit}\n`;
1223
+ output += ` Agents: ${agents.length}\n`;
1224
+ if (agents.length > 0) {
1225
+ output += `\nAgents in this world:\n`;
1226
+ agents.forEach(agent => {
1227
+ output += ` - ${agent.name} (${agent.id}) - ${agent.status || 'active'}\n`;
1228
+ });
1229
+ }
1230
+ cliResponse = {
1231
+ success: true,
1232
+ message: output,
1233
+ data: { world: worldData, agents }
1234
+ };
1235
+ }
1236
+ }
1237
+ catch (error) {
1238
+ cliResponse = {
1239
+ success: false,
1240
+ message: 'Failed to get world details',
1241
+ error: error instanceof Error ? error.message : String(error)
1242
+ };
1243
+ }
1244
+ break;
1245
+ case 'updateWorld':
1246
+ try {
1247
+ const existingWorld = await getWorld(collectedParams.name);
1248
+ if (!existingWorld) {
1249
+ cliResponse = {
1250
+ success: false,
1251
+ message: `World '${collectedParams.name}' not found`
1252
+ };
1253
+ break;
1254
+ }
1255
+ // Use enquirer for interactive prompts
1256
+ const prompts = [
1257
+ {
1258
+ type: 'input',
1259
+ name: 'name',
1260
+ message: 'World name:',
1261
+ initial: existingWorld.name
1262
+ },
1263
+ {
1264
+ type: 'input',
1265
+ name: 'description',
1266
+ message: 'World description:',
1267
+ initial: existingWorld.description || ''
1268
+ },
1269
+ {
1270
+ type: 'numeral',
1271
+ name: 'turnLimit',
1272
+ message: 'Turn limit:',
1273
+ initial: existingWorld.turnLimit
1274
+ }
1275
+ ];
1276
+ const answers = await enquirer.prompt(prompts);
1277
+ const updatedWorld = await updateWorld(existingWorld.id, {
1278
+ name: answers.name,
1279
+ description: answers.description,
1280
+ turnLimit: answers.turnLimit
1281
+ });
1282
+ if (updatedWorld) {
1283
+ cliResponse = {
1284
+ success: true,
1285
+ message: `World '${answers.name}' updated successfully`,
1286
+ data: updatedWorld,
1287
+ needsWorldRefresh: true
1288
+ };
1289
+ }
1290
+ else {
1291
+ cliResponse = {
1292
+ success: false,
1293
+ message: 'Failed to update world'
1294
+ };
1295
+ }
1296
+ }
1297
+ catch (error) {
1298
+ cliResponse = {
1299
+ success: false,
1300
+ message: 'Failed to update world',
1301
+ error: error instanceof Error ? error.message : String(error)
1302
+ };
1303
+ }
1304
+ break;
1305
+ case 'deleteWorld':
1306
+ try {
1307
+ const existingWorld = await getWorld(collectedParams.name);
1308
+ if (!existingWorld) {
1309
+ cliResponse = {
1310
+ success: false,
1311
+ message: `World '${collectedParams.name}' not found`
1312
+ };
1313
+ break;
1314
+ }
1315
+ // Confirmation prompt
1316
+ const confirmPrompt = {
1317
+ type: 'confirm',
1318
+ name: 'confirmed',
1319
+ message: `Are you sure you want to delete world '${existingWorld.name}'? This action cannot be undone.`,
1320
+ initial: false
1321
+ };
1322
+ const { confirmed } = await enquirer.prompt(confirmPrompt);
1323
+ if (!confirmed) {
1324
+ cliResponse = {
1325
+ success: true,
1326
+ message: 'World deletion cancelled'
1327
+ };
1328
+ break;
1329
+ }
1330
+ const deleted = await deleteWorld(existingWorld.id);
1331
+ if (deleted) {
1332
+ cliResponse = {
1333
+ success: true,
1334
+ message: `World '${existingWorld.name}' deleted successfully`,
1335
+ needsWorldRefresh: true
1336
+ };
1337
+ }
1338
+ else {
1339
+ cliResponse = {
1340
+ success: false,
1341
+ message: 'Failed to delete world'
1342
+ };
1343
+ }
1344
+ }
1345
+ catch (error) {
1346
+ cliResponse = {
1347
+ success: false,
1348
+ message: 'Failed to delete world',
1349
+ error: error instanceof Error ? error.message : String(error)
1350
+ };
1351
+ }
1352
+ break;
1353
+ // New Agent CRUD commands
1354
+ case 'listAgents':
1355
+ {
1356
+ const worldError = requireWorldOrError(world, command);
1357
+ if (worldError)
1358
+ return worldError;
1359
+ }
1360
+ try {
1361
+ // Get world instance first
1362
+ const worldInstance = await getWorld(world.id);
1363
+ if (!worldInstance)
1364
+ throw new Error(`World not found`);
1365
+ const agents = await listAgents(world.id);
1366
+ if (agents.length === 0) {
1367
+ cliResponse = {
1368
+ success: true,
1369
+ message: `No agents found in world '${world.name}'.`
1370
+ };
1371
+ }
1372
+ else {
1373
+ let output = `\nAgents in world '${world.name}':\n`;
1374
+ agents.forEach(agent => {
1375
+ output += ` Name: ${agent.name} (${agent.id})\n`;
1376
+ output += ` Type: ${agent.type}\n`;
1377
+ output += ` Model: ${agent.model}\n`;
1378
+ output += ` Status: ${agent.status || 'active'}\n`;
1379
+ output += ` Memory Size: ${agent.memory?.length || 0} messages\n`;
1380
+ output += ` LLM Calls: ${agent.llmCallCount}\n`;
1381
+ output += ` Created: ${agent.createdAt ? agent.createdAt.toISOString().split('T')[0] : 'Unknown'}\n`;
1382
+ output += ` Last Active: ${agent.lastActive ? agent.lastActive.toISOString().split('T')[0] : 'Unknown'}\n`;
1383
+ output += ` ---\n`;
1384
+ });
1385
+ cliResponse = {
1386
+ success: true,
1387
+ message: output,
1388
+ data: { agents }
1389
+ };
1390
+ }
1391
+ }
1392
+ catch (error) {
1393
+ cliResponse = {
1394
+ success: false,
1395
+ message: 'Failed to list agents',
1396
+ error: error instanceof Error ? error.message : String(error)
1397
+ };
1398
+ }
1399
+ break;
1400
+ case 'showAgent':
1401
+ {
1402
+ const worldError = requireWorldOrError(world, command);
1403
+ if (worldError)
1404
+ return worldError;
1405
+ }
1406
+ try {
1407
+ const worldInstance = await getWorld(world.id);
1408
+ if (!worldInstance)
1409
+ throw new Error(`World not found`);
1410
+ const agent = await getAgent(world.id, collectedParams.name);
1411
+ if (!agent) {
1412
+ cliResponse = {
1413
+ success: false,
1414
+ message: `Agent '${collectedParams.name}' not found`
1415
+ };
1416
+ }
1417
+ else {
1418
+ let output = `\nAgent Details:\n`;
1419
+ output += ` Name: ${agent.name}\n`;
1420
+ output += ` ID: ${agent.id}\n`;
1421
+ output += ` Type: ${agent.type}\n`;
1422
+ output += ` Provider: ${agent.provider}\n`;
1423
+ output += ` Model: ${agent.model}\n`;
1424
+ output += ` Status: ${agent.status || 'active'}\n`;
1425
+ output += ` Temperature: ${agent.temperature || 'default'}\n`;
1426
+ output += ` Max Tokens: ${agent.maxTokens || 'default'}\n`;
1427
+ output += ` Memory Size: ${agent.memory.length} messages\n`;
1428
+ output += ` LLM Calls: ${agent.llmCallCount}\n`;
1429
+ output += ` Created: ${agent.createdAt ? agent.createdAt.toISOString() : 'Unknown'}\n`;
1430
+ output += ` Last Active: ${agent.lastActive ? agent.lastActive.toISOString() : 'Unknown'}\n`;
1431
+ if (agent.systemPrompt) {
1432
+ output += `\nSystem Prompt:\n${agent.systemPrompt}\n`;
1433
+ }
1434
+ cliResponse = {
1435
+ success: true,
1436
+ message: output,
1437
+ data: { agent }
1438
+ };
1439
+ }
1440
+ }
1441
+ catch (error) {
1442
+ cliResponse = {
1443
+ success: false,
1444
+ message: 'Failed to get agent details',
1445
+ error: error instanceof Error ? error.message : String(error)
1446
+ };
1447
+ }
1448
+ break;
1449
+ case 'updateAgent':
1450
+ {
1451
+ const worldError = requireWorldOrError(world, command);
1452
+ if (worldError)
1453
+ return worldError;
1454
+ }
1455
+ try {
1456
+ const worldInstance = await getWorld(world.id);
1457
+ if (!worldInstance)
1458
+ throw new Error(`World not found`);
1459
+ const existingAgent = await getAgent(world.id, collectedParams.name);
1460
+ if (!existingAgent) {
1461
+ cliResponse = {
1462
+ success: false,
1463
+ message: `Agent '${collectedParams.name}' not found`
1464
+ };
1465
+ break;
1466
+ }
1467
+ // Use enquirer for interactive prompts with multiline support
1468
+ const prompts = [
1469
+ {
1470
+ type: 'input',
1471
+ name: 'name',
1472
+ message: 'Agent name:',
1473
+ initial: existingAgent.name
1474
+ },
1475
+ {
1476
+ type: 'select',
1477
+ name: 'provider',
1478
+ message: 'LLM Provider:',
1479
+ choices: Object.values(LLMProvider),
1480
+ initial: existingAgent.provider
1481
+ },
1482
+ {
1483
+ type: 'input',
1484
+ name: 'model',
1485
+ message: 'Model:',
1486
+ initial: existingAgent.model
1487
+ },
1488
+ {
1489
+ type: 'input',
1490
+ name: 'systemPrompt',
1491
+ message: 'System prompt (or press Enter for default):',
1492
+ initial: existingAgent.systemPrompt || ''
1493
+ },
1494
+ {
1495
+ type: 'numeral',
1496
+ name: 'temperature',
1497
+ message: 'Temperature (0.0-2.0):',
1498
+ initial: existingAgent.temperature || 0.7
1499
+ },
1500
+ {
1501
+ type: 'numeral',
1502
+ name: 'maxTokens',
1503
+ message: 'Max tokens:',
1504
+ initial: existingAgent.maxTokens || 4096
1505
+ }
1506
+ ];
1507
+ const answers = await enquirer.prompt(prompts);
1508
+ const updatedAgent = await updateAgent(world.id, existingAgent.id, {
1509
+ name: answers.name,
1510
+ provider: answers.provider,
1511
+ model: answers.model,
1512
+ systemPrompt: answers.systemPrompt,
1513
+ temperature: answers.temperature,
1514
+ maxTokens: answers.maxTokens
1515
+ });
1516
+ if (updatedAgent) {
1517
+ cliResponse = {
1518
+ success: true,
1519
+ message: `Agent '${answers.name}' updated successfully`,
1520
+ data: updatedAgent,
1521
+ needsWorldRefresh: true
1522
+ };
1523
+ }
1524
+ else {
1525
+ cliResponse = {
1526
+ success: false,
1527
+ message: 'Failed to update agent'
1528
+ };
1529
+ }
1530
+ }
1531
+ catch (error) {
1532
+ cliResponse = {
1533
+ success: false,
1534
+ message: 'Failed to update agent',
1535
+ error: error instanceof Error ? error.message : String(error)
1536
+ };
1537
+ }
1538
+ break;
1539
+ case 'deleteAgent':
1540
+ {
1541
+ const worldError = requireWorldOrError(world, command);
1542
+ if (worldError)
1543
+ return worldError;
1544
+ }
1545
+ try {
1546
+ const worldInstance = await getWorld(world.id);
1547
+ if (!worldInstance)
1548
+ throw new Error(`World not found`);
1549
+ const existingAgent = await getAgent(world.id, collectedParams.name);
1550
+ if (!existingAgent) {
1551
+ cliResponse = {
1552
+ success: false,
1553
+ message: `Agent '${collectedParams.name}' not found`
1554
+ };
1555
+ break;
1556
+ }
1557
+ // Confirmation prompt
1558
+ const confirmPrompt = {
1559
+ type: 'confirm',
1560
+ name: 'confirmed',
1561
+ message: `Are you sure you want to delete agent '${existingAgent.name}'? This action cannot be undone.`,
1562
+ initial: false
1563
+ };
1564
+ const { confirmed } = await enquirer.prompt(confirmPrompt);
1565
+ if (!confirmed) {
1566
+ cliResponse = {
1567
+ success: true,
1568
+ message: 'Agent deletion cancelled'
1569
+ };
1570
+ break;
1571
+ }
1572
+ const deleted = await deleteAgent(world.id, existingAgent.id);
1573
+ if (deleted) {
1574
+ cliResponse = {
1575
+ success: true,
1576
+ message: `Agent '${existingAgent.name}' deleted successfully`,
1577
+ needsWorldRefresh: true
1578
+ };
1579
+ }
1580
+ else {
1581
+ cliResponse = {
1582
+ success: false,
1583
+ message: 'Failed to delete agent'
1584
+ };
1585
+ }
1586
+ }
1587
+ catch (error) {
1588
+ cliResponse = {
1589
+ success: false,
1590
+ message: 'Failed to delete agent',
1591
+ error: error instanceof Error ? error.message : String(error)
1592
+ };
1593
+ }
1594
+ break;
1595
+ case 'exportWorld':
1596
+ {
1597
+ const worldError = requireWorldOrError(world, command);
1598
+ if (worldError)
1599
+ return worldError;
1600
+ }
1601
+ try {
1602
+ const fileParam = collectedParams.file;
1603
+ cliResponse = await exportWorldToMarkdownFile(world.name, fileParam);
1604
+ }
1605
+ catch (error) {
1606
+ cliResponse = {
1607
+ success: false,
1608
+ message: 'Failed to export world',
1609
+ error: error instanceof Error ? error.message : String(error)
1610
+ };
1611
+ }
1612
+ break;
1613
+ case 'saveWorld':
1614
+ {
1615
+ const worldError = requireWorldOrError(world, command);
1616
+ if (worldError)
1617
+ return worldError;
1618
+ }
1619
+ try {
1620
+ cliResponse = await saveWorldToStorage(world);
1621
+ }
1622
+ catch (error) {
1623
+ cliResponse = {
1624
+ success: false,
1625
+ message: 'Failed to save world',
1626
+ error: error instanceof Error ? error.message : String(error)
1627
+ };
1628
+ }
1629
+ break;
1630
+ // Chat history commands
1631
+ case 'listChats': {
1632
+ const worldError = requireWorldOrError(world, command);
1633
+ if (worldError)
1634
+ return worldError;
1635
+ const filter = collectedParams.filter ? String(collectedParams.filter).toLowerCase() : undefined;
1636
+ if (filter && filter !== '--active') {
1637
+ cliResponse = {
1638
+ success: false,
1639
+ message: `Unknown filter '${collectedParams.filter}'. Did you mean --active?`
1640
+ };
1641
+ break;
1642
+ }
1643
+ try {
1644
+ const chats = await listChats(world.id);
1645
+ const worldState = await getWorld(world.id);
1646
+ const currentChatId = worldState?.currentChatId || null;
1647
+ if (filter === '--active') {
1648
+ if (!currentChatId) {
1649
+ cliResponse = {
1650
+ success: true,
1651
+ message: `World '${world.name}' does not have an active chat.`
1652
+ };
1653
+ break;
1654
+ }
1655
+ const activeChat = chats.find(chat => chat.id === currentChatId);
1656
+ if (!activeChat) {
1657
+ cliResponse = {
1658
+ success: false,
1659
+ message: `Active chat '${currentChatId}' not found.`,
1660
+ technicalDetails: 'Active chat ID missing from storage'
1661
+ };
1662
+ break;
1663
+ }
1664
+ let output = `\nCurrent chat in world '${world.name}':\n`;
1665
+ output += ` ID: ${activeChat.id}\n`;
1666
+ output += ` Name: ${activeChat.name}\n`;
1667
+ if (activeChat.description)
1668
+ output += ` Description: ${activeChat.description}\n`;
1669
+ output += ` Messages: ${activeChat.messageCount}\n`;
1670
+ output += ` Updated: ${activeChat.updatedAt.toISOString()}\n`;
1671
+ cliResponse = {
1672
+ success: true,
1673
+ message: output,
1674
+ data: { chats: [activeChat], currentChatId }
1675
+ };
1676
+ break;
1677
+ }
1678
+ if (chats.length === 0) {
1679
+ cliResponse = {
1680
+ success: true,
1681
+ message: `No chat history found in world '${world.name}'.`
1682
+ };
1683
+ }
1684
+ else {
1685
+ let output = `\nChat history in world '${world.name}':\n`;
1686
+ chats.forEach((chat) => {
1687
+ const isCurrent = currentChatId && chat.id === currentChatId;
1688
+ output += ` ID: ${chat.id}${isCurrent ? ' (current)' : ''}\n`;
1689
+ output += ` Name: ${chat.name}\n`;
1690
+ if (chat.description)
1691
+ output += ` Description: ${chat.description}\n`;
1692
+ output += ` Messages: ${chat.messageCount}\n`;
1693
+ output += ` Created: ${chat.createdAt.toISOString().split('T')[0]}\n`;
1694
+ output += ` Updated: ${chat.updatedAt.toISOString().split('T')[0]}\n`;
1695
+ output += ` ---\n`;
1696
+ });
1697
+ cliResponse = {
1698
+ success: true,
1699
+ message: output,
1700
+ data: { chats, currentChatId }
1701
+ };
1702
+ }
1703
+ }
1704
+ catch (error) {
1705
+ cliResponse = {
1706
+ success: false,
1707
+ message: 'Failed to list chat history',
1708
+ error: error instanceof Error ? error.message : String(error)
1709
+ };
1710
+ }
1711
+ break;
1712
+ }
1713
+ case 'createChat':
1714
+ {
1715
+ const worldError = requireWorldOrError(world, command);
1716
+ if (worldError)
1717
+ return worldError;
1718
+ }
1719
+ try {
1720
+ // Create a simple new chat using the available API
1721
+ const updatedWorld = await newChat(world.id);
1722
+ if (updatedWorld) {
1723
+ cliResponse = {
1724
+ success: true,
1725
+ message: `New chat created successfully for world '${updatedWorld.name}'`,
1726
+ data: { worldId: updatedWorld.id, currentChatId: updatedWorld.currentChatId }
1727
+ };
1728
+ }
1729
+ else {
1730
+ cliResponse = {
1731
+ success: false,
1732
+ message: 'Failed to create new chat',
1733
+ error: 'Unknown error occurred'
1734
+ };
1735
+ }
1736
+ }
1737
+ catch (error) {
1738
+ cliResponse = {
1739
+ success: false,
1740
+ message: 'Failed to create chat',
1741
+ error: error instanceof Error ? error.message : String(error)
1742
+ };
1743
+ }
1744
+ break;
1745
+ case 'selectChat':
1746
+ {
1747
+ const worldError = requireWorldOrError(world, command);
1748
+ if (worldError)
1749
+ return worldError;
1750
+ }
1751
+ try {
1752
+ // Get all chats for the world
1753
+ const chats = await listChats(world.id);
1754
+ if (chats.length === 0) {
1755
+ cliResponse = {
1756
+ success: true,
1757
+ message: `No chat history found in world '${world.name}'.`
1758
+ };
1759
+ break;
1760
+ }
1761
+ // Get current chat ID
1762
+ const worldState = await getWorld(world.id);
1763
+ const currentChatId = worldState?.currentChatId || null;
1764
+ // If only one chat, auto-select it
1765
+ if (chats.length === 1) {
1766
+ const chat = chats[0];
1767
+ console.log(`\n${boldGreen('Auto-selecting the only available chat:')} ${cyan(chat.name)} (${gray(chat.id)})`);
1768
+ // Restore the chat
1769
+ const restored = await restoreChat(world.id, chat.id);
1770
+ if (!restored) {
1771
+ cliResponse = {
1772
+ success: false,
1773
+ message: `Failed to restore chat '${chat.id}'`
1774
+ };
1775
+ break;
1776
+ }
1777
+ // Display chat messages
1778
+ await displayChatMessages(world.id, chat.id);
1779
+ cliResponse = {
1780
+ success: true,
1781
+ message: `Chat '${chat.name}' selected and loaded`,
1782
+ data: { worldId: restored.id, currentChatId: restored.currentChatId },
1783
+ needsWorldRefresh: true
1784
+ };
1785
+ break;
1786
+ }
1787
+ // Return data for interactive chat selection in CLI
1788
+ cliResponse = {
1789
+ success: true,
1790
+ message: 'Opening chat selection...',
1791
+ data: {
1792
+ selectChat: true,
1793
+ chats,
1794
+ currentChatId
1795
+ }
1796
+ };
1797
+ }
1798
+ catch (error) {
1799
+ cliResponse = {
1800
+ success: false,
1801
+ message: 'Failed to select chat',
1802
+ error: error instanceof Error ? error.message : String(error)
1803
+ };
1804
+ }
1805
+ break;
1806
+ case 'loadChat':
1807
+ {
1808
+ const worldError = requireWorldOrError(world, command);
1809
+ if (worldError)
1810
+ return worldError;
1811
+ }
1812
+ try {
1813
+ // Simplified: use restoreChat function directly
1814
+ const restored = await restoreChat(world.id, collectedParams.chatId);
1815
+ if (!restored) {
1816
+ cliResponse = {
1817
+ success: false,
1818
+ message: `Chat '${collectedParams.chatId}' not found or could not be restored`
1819
+ };
1820
+ break;
1821
+ }
1822
+ cliResponse = {
1823
+ success: true,
1824
+ message: `Successfully restored world state from chat '${collectedParams.chatId}'`,
1825
+ data: { worldId: restored.id, currentChatId: restored.currentChatId },
1826
+ needsWorldRefresh: true
1827
+ };
1828
+ }
1829
+ catch (error) {
1830
+ cliResponse = {
1831
+ success: false,
1832
+ message: 'Failed to load chat',
1833
+ error: error instanceof Error ? error.message : String(error)
1834
+ };
1835
+ }
1836
+ break;
1837
+ case 'deleteChat':
1838
+ {
1839
+ const worldError = requireWorldOrError(world, command);
1840
+ if (worldError)
1841
+ return worldError;
1842
+ }
1843
+ try {
1844
+ // Simplified: use deleteChat function directly
1845
+ const deleted = await deleteChat(world.id, collectedParams.chatId);
1846
+ if (deleted) {
1847
+ cliResponse = {
1848
+ success: true,
1849
+ message: `Chat '${collectedParams.chatId}' deleted successfully`
1850
+ };
1851
+ }
1852
+ else {
1853
+ cliResponse = {
1854
+ success: false,
1855
+ message: 'Failed to delete chat - chat may not exist'
1856
+ };
1857
+ }
1858
+ }
1859
+ catch (error) {
1860
+ cliResponse = {
1861
+ success: false,
1862
+ message: 'Failed to delete chat',
1863
+ error: error instanceof Error ? error.message : String(error)
1864
+ };
1865
+ }
1866
+ break;
1867
+ case 'renameChat': {
1868
+ const worldError = requireWorldOrError(world, command);
1869
+ if (worldError)
1870
+ return worldError;
1871
+ try {
1872
+ const updatedChat = await updateChat(world.id, collectedParams.chatId, {
1873
+ name: collectedParams.name,
1874
+ description: collectedParams.description
1875
+ });
1876
+ if (!updatedChat) {
1877
+ cliResponse = {
1878
+ success: false,
1879
+ message: `Chat '${collectedParams.chatId}' not found`
1880
+ };
1881
+ break;
1882
+ }
1883
+ cliResponse = {
1884
+ success: true,
1885
+ message: `Chat '${collectedParams.chatId}' renamed to '${updatedChat.name}'`,
1886
+ data: updatedChat,
1887
+ needsWorldRefresh: true
1888
+ };
1889
+ }
1890
+ catch (error) {
1891
+ cliResponse = {
1892
+ success: false,
1893
+ message: 'Failed to rename chat',
1894
+ error: error instanceof Error ? error.message : String(error)
1895
+ };
1896
+ }
1897
+ break;
1898
+ }
1899
+ case 'exportChat': {
1900
+ const worldError = requireWorldOrError(world, command);
1901
+ if (worldError)
1902
+ return worldError;
1903
+ const chatId = collectedParams.chatId || world.currentChatId;
1904
+ if (!chatId) {
1905
+ cliResponse = {
1906
+ success: false,
1907
+ message: `No chat selected. Use /chat list --active to see the current chat.`
1908
+ };
1909
+ break;
1910
+ }
1911
+ try {
1912
+ cliResponse = await exportChatToMarkdownFile(world.id, world.name, chatId, collectedParams.file);
1913
+ }
1914
+ catch (error) {
1915
+ cliResponse = {
1916
+ success: false,
1917
+ message: 'Failed to export chat',
1918
+ error: error instanceof Error ? error.message : String(error)
1919
+ };
1920
+ }
1921
+ break;
1922
+ }
1923
+ case 'quit':
1924
+ case 'exit':
1925
+ cliResponse = {
1926
+ success: true,
1927
+ message: 'Exiting CLI...',
1928
+ data: { exit: true }
1929
+ };
1930
+ break;
1931
+ default:
1932
+ cliResponse = { success: false, message: `Unknown command type: ${commandInfo.type}`, data: null };
1933
+ }
1934
+ // Signal CLI to refresh subscription if needed
1935
+ if (cliResponse.needsWorldRefresh) {
1936
+ cliResponse.refreshWorld = true;
1937
+ }
1938
+ return cliResponse;
1939
+ }
1940
+ catch (error) {
1941
+ return {
1942
+ success: false,
1943
+ message: 'Command execution failed',
1944
+ technicalDetails: error instanceof Error ? error.message : String(error)
1945
+ };
1946
+ }
1947
+ }
1948
+ // Main CLI input processor - handles both commands and messages
1949
+ export async function processCLIInput(input, world, sender = 'human') {
1950
+ const context = {
1951
+ currentWorld: world,
1952
+ currentWorldName: world?.name
1953
+ };
1954
+ // Simple prompt function for CLI
1955
+ const promptFunction = async (question, options) => {
1956
+ console.log(question);
1957
+ if (options && options.length > 0) {
1958
+ console.log(`Options: ${options.join(', ')}`);
1959
+ }
1960
+ return new Promise((resolve) => {
1961
+ const rl = readline.createInterface({
1962
+ input: process.stdin,
1963
+ output: process.stdout
1964
+ });
1965
+ rl.question('> ', (answer) => {
1966
+ rl.close();
1967
+ resolve(answer);
1968
+ });
1969
+ });
1970
+ };
1971
+ // Process commands (starting with '/')
1972
+ if (input.trim().startsWith('/')) {
1973
+ const parsed = parseCLICommand(input);
1974
+ if (!parsed.isValid) {
1975
+ return {
1976
+ success: false,
1977
+ message: parsed.error || 'Invalid command',
1978
+ technicalDetails: `Failed to parse command: ${input}`
1979
+ };
1980
+ }
1981
+ return await processCLICommand(input, context, promptFunction);
1982
+ }
1983
+ // Handle messages to the current world
1984
+ if (!world) {
1985
+ return {
1986
+ success: false,
1987
+ message: 'Cannot send message - no world selected',
1988
+ technicalDetails: 'Message requires world context'
1989
+ };
1990
+ }
1991
+ try {
1992
+ const currentChatId = String(world?.currentChatId || '').trim();
1993
+ if (!currentChatId) {
1994
+ return {
1995
+ success: false,
1996
+ message: 'Cannot send message - no active chat session',
1997
+ technicalDetails: 'Message requires an active chat context'
1998
+ };
1999
+ }
2000
+ const restoredWorld = await restoreChat(world.id, currentChatId);
2001
+ if (!restoredWorld || restoredWorld.currentChatId !== currentChatId) {
2002
+ return {
2003
+ success: false,
2004
+ message: `Cannot send message - chat not found: ${currentChatId}`,
2005
+ technicalDetails: `Failed to restore active chat '${currentChatId}' before message publish`
2006
+ };
2007
+ }
2008
+ publishMessage(world, input, sender, currentChatId);
2009
+ return {
2010
+ success: true,
2011
+ message: '',
2012
+ data: { sender, chatId: currentChatId },
2013
+ technicalDetails: `Message published to world '${world.name}' (chat '${currentChatId}')`
2014
+ };
2015
+ }
2016
+ catch (error) {
2017
+ return {
2018
+ success: false,
2019
+ message: 'Failed to send message',
2020
+ error: error instanceof Error ? error.message : String(error),
2021
+ technicalDetails: `Error publishing message to world '${world.name}': ${error instanceof Error ? error.message : error}`
2022
+ };
2023
+ }
2024
+ }