@j-o-r/hello-dave 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +445 -160
  3. package/README.md.backup +269 -0
  4. package/README.md.bak +481 -0
  5. package/README.md.bak.1774780058 +338 -0
  6. package/README.md.bak2 +455 -0
  7. package/bin/dave.js +165 -0
  8. package/docs.bak.1774780058/agent-manager.md +167 -0
  9. package/docs.bak.1774780058/agent-manager.md.bak +137 -0
  10. package/docs.bak.1774780058/agent-manager.md.bak2 +157 -0
  11. package/docs.bak.1774780058/codeserver-pattern.md +191 -0
  12. package/docs.bak.1774780058/path-resolution-best-practices.md +104 -0
  13. package/docs.bak.1774780058/project-overview.md +67 -0
  14. package/docs.bak.1774780058/project-overview.md.bak +67 -0
  15. package/docs.bak.1774780058/prompt-class.md +141 -0
  16. package/docs.bak.1774780058/prompt-class.md.bak +142 -0
  17. package/docs.bak.1774780058/tools-syntax-validation.md +121 -0
  18. package/docs.bak.1774780058/tools-syntax-validation.md.bak2 +125 -0
  19. package/docs.bak.1774780058/tools-syntax-validation.md.bak3 +125 -0
  20. package/docs.bak.1774780058/tools-syntax-validation.md.bak4 +106 -0
  21. package/docs.bak.1774780058/tools-syntax-validation.md.bak_path +106 -0
  22. package/docs.bak.1774780058/toolset.md +164 -0
  23. package/docs.bak.1774780058/toolset.md.bak +94 -0
  24. package/docs.bak.1774780058/toolset.md.bak3 +161 -0
  25. package/docs.bak.1774780058/toolset.md.bak4 +161 -0
  26. package/docs.bak.1774780058/toolset.md.bak5 +161 -0
  27. package/docs.bak.1774780058/toolset.md.bak6 +163 -0
  28. package/docs.bak.1774780058/toolset.md.bak_path +163 -0
  29. package/docs.bak.1774780058/toolset.md.bak_syntax +161 -0
  30. package/docs.bak.1774780058/xai-responses.md +111 -0
  31. package/docs.bak.1774780058/xai-responses.md.bak +107 -0
  32. package/docs.bak.1774780058/xai-responses.md.bak2 +107 -0
  33. package/docs.bak.1774780058/xai_collections.md +106 -0
  34. package/examples/ask_agent.js +137 -0
  35. package/examples/code_agent.js +149 -0
  36. package/examples/coderev_agent.js +136 -0
  37. package/examples/codeserver.sh +47 -0
  38. package/examples/daisy_agent.js +170 -0
  39. package/examples/docs_agent.js +148 -0
  40. package/examples/gpt_agent.js +125 -0
  41. package/examples/grok_agent.js +132 -0
  42. package/examples/grok_agent.js.bak +98 -0
  43. package/examples/grok_agent.js.bak.2 +99 -0
  44. package/examples/grok_agent.js.bak.3 +1 -0
  45. package/examples/grok_agent.js.bak.4 +124 -0
  46. package/examples/grok_agent.js.bak.5 +1 -0
  47. package/examples/grok_agent.js.bak.6 +1 -0
  48. package/examples/memory_agent.js +152 -0
  49. package/examples/npm_agent.js +202 -0
  50. package/examples/npm_agent.js.bak.3 +2 -0
  51. package/examples/npm_agent.js.bak.4 +205 -0
  52. package/examples/npm_agent.js.bak.5 +1 -0
  53. package/examples/npm_agent.js.bak.6 +1 -0
  54. package/examples/prompt_agent.js +133 -0
  55. package/examples/readme_agent.js +148 -0
  56. package/examples/spawn_agent.js +293 -0
  57. package/examples/test_agent.js +187 -0
  58. package/examples/todo_agent.js +175 -0
  59. package/examples.bak.1774780058/ask_agent.js +114 -0
  60. package/examples.bak.1774780058/code_agent.js +149 -0
  61. package/examples.bak.1774780058/coderev_agent.js +72 -0
  62. package/examples.bak.1774780058/codeserver.sh +47 -0
  63. package/examples.bak.1774780058/daisy_agent.js +177 -0
  64. package/examples.bak.1774780058/docs_agent.js +119 -0
  65. package/{bin/hdAsk.js → examples.bak.1774780058/gpt_agent.js} +46 -40
  66. package/examples.bak.1774780058/grok_agent.js +98 -0
  67. package/examples.bak.1774780058/memory_agent.js +112 -0
  68. package/examples.bak.1774780058/npm_agent.js +175 -0
  69. package/examples.bak.1774780058/prompt_agent.js +112 -0
  70. package/examples.bak.1774780058/readme_agent.js +144 -0
  71. package/examples.bak.1774780058/spawn_agent.js +263 -0
  72. package/examples.bak.1774780058/test_agent.js +162 -0
  73. package/examples.bak.1774780058/todo_agent.js +138 -0
  74. package/lib/API/openai.com/reponses/text.js +12 -18
  75. package/lib/API/x.ai/collections.js +354 -0
  76. package/lib/API/x.ai/files.js +218 -0
  77. package/lib/API/x.ai/responses.js +492 -0
  78. package/lib/API/x.ai/text.js +1 -1
  79. package/lib/AgentClient.js +13 -6
  80. package/lib/AgentManager.js +80 -10
  81. package/lib/AgentServer.js +50 -22
  82. package/lib/Cli.js +7 -1
  83. package/lib/Prompt.js +4 -2
  84. package/lib/ToolSet.js +2 -1
  85. package/lib/genericToolset.js +258 -88
  86. package/lib/genericToolset.js.bak_syntax +402 -0
  87. package/lib/index.js +4 -2
  88. package/lib/wsCli.js +256 -0
  89. package/lib/wsIO.js +96 -0
  90. package/package.json +26 -21
  91. package/scenarios.bak.1774780058/data/eval_node_message.json +9 -0
  92. package/scenarios.bak.1774780058/data/hist_oa.json +66 -0
  93. package/scenarios.bak.1774780058/data/o3_response1.json +96 -0
  94. package/scenarios.bak.1774780058/data/oa_reasoning_parse.json +112 -0
  95. package/scenarios.bak.1774780058/data/tool_oa.json +96 -0
  96. package/scenarios.bak.1774780058/data/tool_xai.json +59 -0
  97. package/scenarios.bak.1774780058/data/tool_xai2.json +40 -0
  98. package/scenarios.bak.1774780058/data/xai-response-1.json +59 -0
  99. package/scenarios.bak.1774780058/data/xai-response-2.json +10 -0
  100. package/scenarios.bak.1774780058/data/xai_reasoning_tools_resp.json +59 -0
  101. package/scenarios.bak.1774780058/data/xai_search_response.json +58 -0
  102. package/scenarios.bak.1774780058/environment.js +10 -0
  103. package/scenarios.bak.1774780058/example.js +17 -0
  104. package/scenarios.bak.1774780058/genericToolset.test.js +182 -0
  105. package/scenarios.bak.1774780058/grok.js +113 -0
  106. package/scenarios.bak.1774780058/memory-tools.js +51 -0
  107. package/scenarios.bak.1774780058/openai-o3.js +137 -0
  108. package/scenarios.bak.1774780058/openai-prompt.js +155 -0
  109. package/scenarios.bak.1774780058/openai-session.js +148 -0
  110. package/scenarios.bak.1774780058/openai.js +102 -0
  111. package/scenarios.bak.1774780058/prompt.js +118 -0
  112. package/scenarios.bak.1774780058/promptFishbowl.js +76 -0
  113. package/scenarios.bak.1774780058/search.brave.com.js +25 -0
  114. package/scenarios.bak.1774780058/sh.js +15 -0
  115. package/scenarios.bak.1774780058/test-wsio.js +26 -0
  116. package/scenarios.bak.1774780058/testToolset.js +42 -0
  117. package/scenarios.bak.1774780058/toolset.js +16 -0
  118. package/scenarios.bak.1774780058/toolset.test.js +141 -0
  119. package/scenarios.bak.1774780058/write_file_syntax.test.js +145 -0
  120. package/scenarios.bak.1774780058/write_file_validation/README.md +30 -0
  121. package/scenarios.bak.1774780058/write_file_validation/bad.js +3 -0
  122. package/scenarios.bak.1774780058/write_file_validation/good.js +4 -0
  123. package/scenarios.bak.1774780058/write_file_validation/test.sh +43 -0
  124. package/scenarios.bak.1774780058/wsClient.js +69 -0
  125. package/scenarios.bak.1774780058/xai_responses.integration.test.js +57 -0
  126. package/scenarios.bak.1774780058/xai_responses.test.js +154 -0
  127. package/scenarios.bak.1774780058/xaicoll.js +50 -0
  128. package/scenarios.bak.1774780058/xaifiles.js +48 -0
  129. package/types/API/openai.com/reponses/text.d.ts +17 -3
  130. package/types/API/x.ai/collections.d.ts +167 -0
  131. package/types/API/x.ai/files.d.ts +84 -0
  132. package/types/API/x.ai/responses.d.ts +379 -0
  133. package/types/AgentClient.d.ts +5 -0
  134. package/types/AgentManager.d.ts +25 -31
  135. package/types/AgentServer.d.ts +5 -1
  136. package/types/Prompt.d.ts +4 -2
  137. package/types/ToolSet.d.ts +1 -0
  138. package/types/index.d.ts +4 -3
  139. package/types/wsCli.d.ts +3 -0
  140. package/types/wsIO.d.ts +26 -0
  141. package/utils/bars.js +40 -0
  142. package/utils/clear_sessions.sh +54 -0
  143. package/{bin/hdInspect.js → utils/format_log.js} +5 -0
  144. package/utils/list_sessions.sh +46 -0
  145. package/utils/search_sessions.sh +73 -0
  146. package/utils/syntax_check.sh +61 -0
  147. package/utils/test.sh +46 -0
  148. package/bin/hdClear.js +0 -13
  149. package/bin/hdCode.js +0 -115
  150. package/bin/hdConnect.js +0 -230
  151. package/bin/hdNpm.js +0 -114
  152. package/bin/hdPrompt.js +0 -108
  153. package/examples/claude-test.js +0 -89
  154. package/examples/claude.js +0 -143
  155. package/examples/gpt.js +0 -127
  156. package/examples/gpt_code.js +0 -125
  157. package/examples/gpt_note_keeping.js +0 -117
  158. package/examples/grok.js +0 -119
  159. package/examples/grok_code.js +0 -114
  160. package/examples/grok_note_keeping.js +0 -111
  161. package/module.md +0 -189
@@ -0,0 +1,402 @@
1
+ import { SH, bashEscape } from '@j-o-r/sh'
2
+ import { ToolSet, env } from './index.js'
3
+ import path from 'node:path';
4
+ import { promises as fs } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const user = await env();
11
+ const environment = `
12
+ Name: ${user.name}
13
+ System: ${user.system}
14
+ City: ${user.city}
15
+ Region: ${user.region}
16
+ Country: ${user.country}
17
+ Timezone: ${user.timezone}
18
+ ExternalIp: ${user.external_ip}
19
+ `.trim();
20
+ const tools = new ToolSet('auto');
21
+
22
+ /**
23
+ * reduce the error output to essential info only, if possible
24
+ * @param {string} errorStr
25
+ * @returns {string}
26
+ */
27
+ const getJSError = (errorStr) => {
28
+ let result = '';
29
+ const linematch = errorStr.match(/\[eval\]:(\d+)/);
30
+ const lineNumber = linematch ? linematch[1] : '';
31
+ const match = errorStr.split(/\" \"\[eval\]:\d+/s);
32
+ if (match.length > 1) {
33
+ // Remove last 10 lines
34
+ const res = match[1].split('\n').slice(0, -10).join('\n');
35
+ result = `Error: line ${lineNumber}\n${res} `
36
+ } else {
37
+ result = errorStr;
38
+ }
39
+ return result;
40
+ }
41
+
42
+ tools.add(
43
+ 'javascript_interpreter',
44
+ `Execute ESM ES6 javascript on \`node\`.`,
45
+ {
46
+ type: 'object',
47
+ properties: {
48
+ script: {
49
+ type: 'string',
50
+ description: `ES6 ESM Javascript eval. 'console.log' to capture the response. cwd: ${user.cwd}`,
51
+ }
52
+ },
53
+ required: ['script']
54
+ },
55
+ async (params) => {
56
+ let response = '';
57
+ try {
58
+ const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`;
59
+ response = await SH`cat <<'${delim}' | node --input-type=module -
60
+ ${params.script}
61
+ ${delim}
62
+ `.run();
63
+ } catch (e) {
64
+ const errorStr = e.toString();
65
+ response = getJSError(errorStr);
66
+ }
67
+ return response;
68
+ }
69
+ );
70
+ tools.add(
71
+ 'get_user_env', // name
72
+ 'Get the user location, name and OS environment', // desciption
73
+ {
74
+ type: 'object',
75
+ properties: {
76
+ }
77
+ },
78
+ async (_params) => {
79
+ return environment;
80
+ }
81
+ );
82
+
83
+ tools.add(
84
+ 'execute_bash_script',
85
+ 'Execute a bash script or command. (char escaping not needed)',
86
+ {
87
+ type: 'object',
88
+ properties: {
89
+ bash_script: { type: 'string', description: `RAW bash script (LITERAL TEXT: NO ESCAPING—output $, |, <, >, &, ", ', \\, \` , newlines, $(()), [[ ]] verbatim. Heredoc delimiter handles safely. EX: echo "$((1+1)) | grep \'<&>\"hi$USER\"\' && ls -la\`. (${user.system})` }
90
+ },
91
+ required: ['bash_script']
92
+ },
93
+ async (params) => {
94
+ const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
95
+ return await SH`bash <<'${delim}'
96
+ ${params.bash_script}
97
+ ${delim}
98
+ `.run()
99
+ }
100
+ );
101
+
102
+ tools.add(
103
+ 'send_email',
104
+ 'Send an email.',
105
+ {
106
+ type: 'object',
107
+ properties: {
108
+ to: { type: 'string', description: 'Recipient email' },
109
+ subject: { type: 'string', description: 'Subject' },
110
+ body: { type: 'string', description: 'Message body' }
111
+ },
112
+ required: ['to', 'subject', 'body']
113
+ },
114
+ async (params) => {
115
+ const delim = `END_EMAIL_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
116
+ return await SH`msmtp ${params.to} <<'${delim}'
117
+ To: ${params.to}
118
+ Subject: ${params.subject}
119
+
120
+ ${params.body}
121
+ ${delim}
122
+ `.run();
123
+ }
124
+ );
125
+ tools.add(
126
+ 'open_link',
127
+ 'Open an url or file in the local user environment. (xdg-open)',
128
+ {
129
+ type: 'object',
130
+ properties: {
131
+ url: { type: 'string', description: 'file | URL' }
132
+ },
133
+ required: ['url']
134
+ },
135
+ async (params) => {
136
+ return await SH`xdg-open ${[params.url]}`.run();
137
+ }
138
+ );
139
+ tools.add(
140
+ 'execute_remote_script',
141
+ 'Execute bash script on a remote machine via SSH.',
142
+ {
143
+ type: 'object',
144
+ properties: {
145
+ url: { type: 'string', description: 'SSH URL, e.g., ssh://user@host or ssh://user@host:port' },
146
+ script: { type: 'string', description: 'RAW script code to execute remotely (no escaping needed)' }
147
+ },
148
+ required: ['url', 'script']
149
+ },
150
+ async (params) => {
151
+ const { url, script } = params;
152
+ if (!url.startsWith('ssh://')) throw new Error('Invalid SSH URL');
153
+ const withoutProto = url.slice(6);
154
+ const parts = withoutProto.split(':');
155
+ let port = 22;
156
+ let userHost = withoutProto;
157
+ if (parts.length > 1) {
158
+ userHost = parts[0];
159
+ port = parseInt(parts[1]);
160
+ }
161
+ const [user, host] = userHost.split('@');
162
+ if (!user || !host) throw new Error('Invalid SSH URL format: use ssh://user@host[:port]');
163
+
164
+ const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
165
+ return await SH`ssh -p ${port} ${user}@${host} bash <<'${delim}'
166
+ ${script}
167
+ ${delim}
168
+ `.run();
169
+
170
+ }
171
+ );
172
+ tools.add(
173
+ 'history_search',
174
+ `Search previous LLM chat sessions or list them hierarchically.
175
+ Example query: "(todo|task)" or "package.json".
176
+ Searches filenames & content in .cache/[app]/[prompt]/sessions/*.ndjson (case-insensitive regex, with context).
177
+ Omit query (or use empty string) to list sessions (see utils/list_sessions.sh).`,
178
+ {
179
+ type: 'object',
180
+ properties: {
181
+ query: {
182
+ type: 'string',
183
+ description: `Search query or regex (quoted for multi-word/regex). Omit or empty for list mode.`
184
+ }
185
+ },
186
+ required: []
187
+ },
188
+ async (params) => {
189
+ const history_search = path.resolve(__dirname, '..', 'utils', 'search_sessions.sh');
190
+ const list_sessions = path.resolve(__dirname, '..', 'utils', 'list_sessions.sh');
191
+ if (typeof params.query === 'string' && params.query.trim() !== '') {
192
+ const escapedQuery = bashEscape(params.query);
193
+ return await SH`${history_search} "${escapedQuery}"`.run();
194
+ } else {
195
+ return await SH`${list_sessions}`.run();
196
+ }
197
+ }
198
+ );
199
+ tools.add(
200
+ 'read_file',
201
+ 'Read the raw content of a file strictly within the current working directory (CWD). Paths must be relative (no leading /, no ..).',
202
+ {
203
+ type: 'object',
204
+ properties: {
205
+ file: {
206
+ type: 'string',
207
+ description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed). cwd: ${user.cwd}`
208
+ }
209
+ },
210
+ required: ['file']
211
+ },
212
+ async (params) => {
213
+ const file = params.file?.trim();
214
+ if (typeof file !== 'string' || !file) {
215
+ throw new Error('Valid relative file path required.');
216
+ }
217
+ if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
218
+ throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
219
+ }
220
+ const resolvedPath = path.resolve(process.cwd(), file);
221
+ if (!resolvedPath.startsWith(process.cwd())) {
222
+ throw new Error(`Path '${file}' escapes CWD scope.`);
223
+ }
224
+ try {
225
+ const content = await fs.readFile(resolvedPath, 'utf8');
226
+ return content;
227
+ } catch (e) {
228
+ throw new Error(`Failed to read '${file}': ${e.message}`);
229
+ }
230
+ }
231
+ );
232
+
233
+ tools.add(
234
+ 'write_file',
235
+ 'Write raw content to a file strictly within the current working directory (CWD). Paths must be relative (no leading /, no ..). Content written as-is (no escaping). **AUTO-VALIDATES** JS/Python/Bash/JSON/etc. via `utils/syntax_check.sh`; chmod +x shebangs. Retries syntax errors force LLM fix.',
236
+ {
237
+ type: 'object',
238
+ properties: {
239
+ file: {
240
+ type: 'string',
241
+ description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed).`
242
+ },
243
+ content: {
244
+ type: 'string',
245
+ description: `Raw content to write (as-is, no char escaping needed; supports newlines, $, |, <, >, &, ", ', \\, etc.).`
246
+ }
247
+ },
248
+ required: ['file', 'content']
249
+ },
250
+ async (params) => {
251
+ const file = params.file?.trim();
252
+ const content = params.content ?? '';
253
+ if (typeof file !== 'string' || !file) {
254
+ throw new Error('Valid relative file path required.');
255
+ }
256
+ if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
257
+ throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
258
+ }
259
+ const resolvedPath = path.resolve(process.cwd(), file);
260
+ if (!resolvedPath.startsWith(process.cwd())) {
261
+ throw new Error(`Path '${file}' escapes CWD scope.`);
262
+ }
263
+ try {
264
+ await fs.writeFile(resolvedPath, content, 'utf8');
265
+
266
+ // AUTO-VALIDATE: Multi-lang syntax check via utils/syntax_check.sh + chmod shebang
267
+ const syntaxChecker = path.resolve(process.cwd(), 'utils/syntax_check.sh');
268
+ const hasShebang = content.startsWith('#!');
269
+
270
+ let validationMsg = '';
271
+ try {
272
+ await SH`${syntaxChecker} ${[resolvedPath]}`.run();
273
+ validationMsg = ' ✓ Multi-lang syntax OK';
274
+ } catch (e) {
275
+ const errPreview = content.slice(0, 1000).split('\n').slice(0, 20).join('\n');
276
+ throw new Error(`❌ SYNTAX ERROR in '${file}' (syntax_check.sh failed):\n${e.message}\n\nPREVIEW:\n${errPreview}\n\n... Fix syntax (quotes/backticks) and retry write_file.`);
277
+ }
278
+ if (hasShebang) {
279
+ await SH`chmod +x ${[resolvedPath]}`.run();
280
+ validationMsg += ' ✓ chmod +x';
281
+ }
282
+
283
+ return `Successfully wrote to '${file}' (${Buffer.byteLength(content, 'utf8')} bytes).${validationMsg}`;
284
+ } catch (e) {
285
+ throw new Error(`Failed to write '${file}': ${e.message}`);
286
+ }
287
+ }
288
+ );
289
+
290
+ tools.add(
291
+ 'syntax_check',
292
+ 'Standalone syntax validation for files (JS/Python/Bash/JSON/etc.) via utils/syntax_check.sh. Detects lang from ext/shebang.',
293
+ {
294
+ type: 'object',
295
+ properties: {
296
+ file: {
297
+ type: 'string',
298
+ description: `Relative path to the file within CWD.`
299
+ }
300
+ },
301
+ required: ['file']
302
+ },
303
+ async (params) => {
304
+ const file = params.file?.trim();
305
+ if (typeof file !== 'string' || !file) {
306
+ throw new Error('Valid relative file path required.');
307
+ }
308
+ const resolvedPath = path.resolve(process.cwd(), file);
309
+ if (!resolvedPath.startsWith(process.cwd())) {
310
+ throw new Error(`Path '${file}' escapes CWD scope.`);
311
+ }
312
+ const syntaxChecker = path.resolve(process.cwd(), 'utils/syntax_check.sh');
313
+ return await SH`${syntaxChecker} ${[resolvedPath]}`.run();
314
+ }
315
+ );
316
+
317
+ tools.add(
318
+ 'memory_write',
319
+ `Persist agent memory for tasks, errors, or user preferences to .cache/memory.ndjson in CWD (${user.cwd}). Use to store decisions, tasks, errors, or prefs for later recall. Agent should use this to avoid repeating work or token burn on loops.`,
320
+ {
321
+ type: 'object',
322
+ properties: {
323
+ category: {
324
+ type: 'string',
325
+ enum: ['tasks', 'errors', 'prefs'],
326
+ description: 'Category: one of "tasks", "errors", "prefs"'
327
+ },
328
+ content: {
329
+ type: 'string',
330
+ description: 'Detailed content (e.g., "Pending task: implement runWithMemory optimization", "Error: loop burning tokens", "Pref: temperature=0.2 for decisions")'
331
+ }
332
+ },
333
+ required: ['category', 'content']
334
+ },
335
+ async ({category, content}) => {
336
+ if (!['tasks', 'errors', 'prefs'].includes(category)) {
337
+ throw new Error(`Invalid category '${category}'. Must be 'tasks', 'errors', or 'prefs'.`);
338
+ }
339
+ const memoryPath = path.join(process.cwd(), '.cache', 'memory.ndjson');
340
+ const dir = path.dirname(memoryPath);
341
+ try {
342
+ await fs.mkdir(dir, { recursive: true });
343
+ } catch (e) {
344
+ // Dir likely exists
345
+ }
346
+ const entry = {
347
+ timestamp: new Date().toISOString(),
348
+ category,
349
+ content: content.trim()
350
+ };
351
+ await fs.appendFile(memoryPath, JSON.stringify(entry) + '\n', 'utf8');
352
+ return `✓ Memory stored: [${category}] ${content.length > 50 ? content.slice(0, 47) + '...' : content}`;
353
+ }
354
+ );
355
+
356
+ tools.add(
357
+ 'memory_recall',
358
+ `Retrieve stored agent memories (tasks, errors, prefs) from .cache/memory.ndjson in CWD (${user.cwd}). Use before acting to check prior decisions/tasks/errors/prefs. Query by keyword or category; empty lists recent.`,
359
+ {
360
+ type: 'object',
361
+ properties: {
362
+ query: {
363
+ type: 'string',
364
+ description: 'Keyword, category (tasks/errors/prefs), or phrase to filter (case-insensitive). Empty/omit lists last 20.'
365
+ }
366
+ },
367
+ required: []
368
+ },
369
+ async (params = {}) => {
370
+ const memoryPath = path.join(process.cwd(), '.cache', 'memory.ndjson');
371
+ let content;
372
+ try {
373
+ content = await fs.readFile(memoryPath, 'utf8');
374
+ } catch (e) {
375
+ return 'No memories stored yet. Use memory_write first.';
376
+ }
377
+ const lines = content.trim().split('\n').filter(l => l.trim());
378
+ const memories = [];
379
+ for (const line of lines) {
380
+ try {
381
+ memories.push(JSON.parse(line));
382
+ } catch {
383
+ // Skip invalid lines
384
+ }
385
+ }
386
+ if (!params.query || !params.query.toString().trim()) {
387
+ const recent = memories.slice(-20).reverse();
388
+ return recent.length
389
+ ? 'Recent memories:\n' + recent.map(m => `• ${m.timestamp.slice(0, 19).replace('T', ' ')} [${m.category}] ${m.content}`).join('\n')
390
+ : 'No memories stored.';
391
+ }
392
+ const q = params.query.toString().toLowerCase();
393
+ const matches = memories.filter(m =>
394
+ m.category.toLowerCase().includes(q) || m.content.toLowerCase().includes(q)
395
+ );
396
+ return matches.length
397
+ ? `Matches for "${params.query}":\n` + matches.slice(0, 20).map(m => `• ${m.timestamp.slice(0, 19).replace('T', ' ')} [${m.category}] ${m.content}`).join('\n')
398
+ : `No memories match "${params.query}".`;
399
+ }
400
+ );
401
+
402
+ export default tools
package/lib/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {request as gpt} from './API/openai.com/reponses/text.js';
2
- import {request as grok} from './API/x.ai/text.js';
2
+ import {request as xai} from './API/x.ai/responses.js';
3
3
  import {request as claude} from './API/anthropic.com/text.js';
4
4
  import {request as brave} from './API/brave.com/search.js';
5
5
 
6
6
  import { env, GLOBAL } from './fafs.js';
7
7
  import ToolSet from './ToolSet.js';
8
8
  import AgentServer from './AgentServer.js';
9
+ import AgentManager from './AgentManager.js';
9
10
  import AgentClient from './AgentClient.js';
10
11
  import Session from './Session.js';
11
12
  import Prompt from './Prompt.js';
@@ -14,7 +15,7 @@ import Cli from './Cli.js';
14
15
  const API = {
15
16
  text:{
16
17
  gpt,
17
- grok,
18
+ xai,
18
19
  claude
19
20
  },
20
21
  search: {
@@ -22,6 +23,7 @@ const API = {
22
23
  },
23
24
  }
24
25
  export {
26
+ AgentManager,
25
27
  AgentServer,
26
28
  AgentClient,
27
29
  Prompt,
package/lib/wsCli.js ADDED
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env -S node
2
+ /*
3
+ * WebSocket client for a hello-dave server with auto-reconnect and improved structure.
4
+ * user CLI
5
+ */
6
+ import cli from '@j-o-r/cli';
7
+ import { WebSocketClient } from "@j-o-r/apiserver";
8
+ import { SH } from '@j-o-r/sh';
9
+
10
+ const OPEN = 1; // WebSocket.OPEN
11
+
12
+
13
+ /**
14
+ * Copy text to the clipboard using xclip.
15
+ * @param {string} text
16
+ * @returns {Promise<void>}
17
+ */
18
+ const copyToClipboard = async (text) => {
19
+ if (typeof text !== 'string') return;
20
+ if (text === '') text = ' ';
21
+ const prams = ['-selection', 'clipboard'];
22
+ await SH`xclip ${prams}`.options({ stdio: 'inherit' }).run(text);
23
+ };
24
+ /**
25
+ * Launch a CLI to an Agent Server
26
+ * @param {string} connectUrl - Websocket server endpoint to connect to
27
+ * @param {string} [secret] - Secret websocket connection key
28
+ */
29
+ export default (connectUrl, secret = '') => {
30
+ let ws;
31
+ let busy = false;
32
+ let lastMessage = '';
33
+ const pendingRequests = new Map();
34
+ if (secret !== '') {
35
+ secret = Buffer.from(secret).toString('base64');
36
+ }
37
+
38
+ /**
39
+ * Connect to the WebSocket server (with handlers).
40
+ * Sets up reconnection on close.
41
+ */
42
+ const connect = () => {
43
+ if (ws && ws.readyState === OPEN) {
44
+ console.log('Already connected');
45
+ return;
46
+ }
47
+ const url = connectUrl + '?wssrc_id=' + secret;
48
+ console.log(`Connecting to ${url}`);
49
+ ws = new WebSocketClient(url);
50
+
51
+ ws.onopen = () => {
52
+ console.log('Connected to server');
53
+ // Send introduction (no pending request for this)
54
+ const id = Date.now();
55
+ ws.send(JSON.stringify({
56
+ action: 'user_introduction',
57
+ content: '',
58
+ id
59
+ }));
60
+ cli.focus('user', true);
61
+ };
62
+
63
+ ws.onmessage = (m) => {
64
+ try {
65
+ const data = JSON.parse(m.data);
66
+ const msgId = data.id;
67
+ if (msgId && pendingRequests.has(msgId)) {
68
+ const { resolve, timeout } = pendingRequests.get(msgId);
69
+ clearTimeout(timeout);
70
+ pendingRequests.delete(msgId);
71
+ resolve(data);
72
+ }
73
+ // Ignore unsolicited messages for now
74
+ } catch (e) {
75
+ console.error('Error parsing WebSocket message:', e, m.data);
76
+ }
77
+ };
78
+
79
+ ws.onclose = (event) => {
80
+ console.log('Connection closed (reconnecting in 5s):', event.code, event.reason || 'No reason');
81
+ // Reject all pending requests
82
+ for (const [id, { reject, timeout }] of pendingRequests) {
83
+ clearTimeout(timeout);
84
+ reject(new Error(`Connection closed (${event.code})`));
85
+ pendingRequests.delete(id);
86
+ }
87
+ // Auto-reconnect
88
+ setTimeout(connect, 5000);
89
+ };
90
+
91
+ ws.onerror = (event) => {
92
+ console.error('WebSocket error:', event.toString());
93
+ };
94
+ };
95
+
96
+ /**
97
+ * Send a message and await response.
98
+ * Handles response actions and UI updates.
99
+ * @param {Object} message - Message object (action, content)
100
+ * @returns {Promise<Object>} Response data
101
+ */
102
+ const sendMessage = async (message) => {
103
+ if (!ws || ws.readyState !== OPEN) {
104
+ throw new Error('WebSocket not connected');
105
+ }
106
+
107
+ const id = Date.now();
108
+ message.id = id;
109
+ busy = true;
110
+ cli.startSpinner();
111
+
112
+ const promise = new Promise((resolve, reject) => {
113
+ const timeout = setTimeout(() => {
114
+ if (pendingRequests.has(id)) {
115
+ pendingRequests.delete(id);
116
+ reject(new Error('Request timeout (12 hours)'));
117
+ }
118
+ }, 1000 * 60 * 60 * 12); // 12 hours
119
+ pendingRequests.set(id, { resolve, reject, timeout });
120
+ });
121
+
122
+ try {
123
+ ws.send(JSON.stringify(message));
124
+ const response = await promise;
125
+
126
+ // Handle specific response actions
127
+ if (response.action === 'server_response') {
128
+ cli.focus('assistant');
129
+ cli.write(response.content);
130
+ lastMessage = response.content;
131
+ } else if (response.action === 'server_info') {
132
+ cli.focus('util');
133
+ cli.write(response.content);
134
+ } else if (response.action === 'server_reset') {
135
+ cli.clear();
136
+ } else if (response.action === 'server_sessionlist') {
137
+ const list = response.content;
138
+ const selected = await cli.select('Select your session: ', list);
139
+ if (selected && selected !== 'NONE') {
140
+ await sendMessage({ action: 'user_loadsession', content: selected });
141
+ }
142
+ } else if (response.action === 'server_sessionloaded') {
143
+ cli.clear();
144
+ const messages = response.content;
145
+ messages.forEach((msg) => {
146
+ cli.focus(msg.role);
147
+ cli.write(msg.content);
148
+ lastMessage = msg.content;
149
+ });
150
+ }
151
+ // Other actions ignored
152
+ return response;
153
+ } catch (e) {
154
+ console.error('Request error:', e.message);
155
+ } finally {
156
+ if (pendingRequests.has(id)) {
157
+ const { timeout } = pendingRequests.get(id);
158
+ clearTimeout(timeout);
159
+ pendingRequests.delete(id);
160
+ }
161
+ busy = false;
162
+ cli.stopSpinner();
163
+ cli.focus('user', true);
164
+ }
165
+ };
166
+
167
+ // Register key mappings
168
+ cli.registerKeyMappings([
169
+ // ALT-C: Clear screen
170
+ {
171
+ name: 'c', ctrl: false, meta: true, shift: false,
172
+ handler: async () => {
173
+ if (!busy) {
174
+ cli.clear();
175
+ }
176
+ }
177
+ },
178
+ // ALT-R: Reset session
179
+ {
180
+ name: 'r', ctrl: false, meta: true, shift: false,
181
+ handler: async () => {
182
+ if (!busy) {
183
+ await sendMessage({ action: 'user_reset', content: '' });
184
+ }
185
+ }
186
+ },
187
+ // ALT-S: Session list
188
+ {
189
+ name: 's', ctrl: false, meta: true, shift: false,
190
+ handler: async () => {
191
+ if (!busy) {
192
+ await sendMessage({ action: 'user_sessionlist', content: '' });
193
+ }
194
+ }
195
+ },
196
+ // ALT-I: Server info
197
+ {
198
+ name: 'i', ctrl: false, meta: true, shift: false,
199
+ handler: async () => {
200
+ if (!busy) {
201
+ await sendMessage({ action: 'user_info', content: '' });
202
+ }
203
+ }
204
+ },
205
+ // ALT-M: Copy last message
206
+ {
207
+ name: 'm', ctrl: false, meta: true, shift: false,
208
+ handler: async () => {
209
+ if (!busy && lastMessage) {
210
+ await copyToClipboard(lastMessage);
211
+ cli.focus('util');
212
+ cli.write('Copied last message to clipboard');
213
+ cli.focus('user', true);
214
+ }
215
+ }
216
+ },
217
+ // CTRL-K: Help
218
+ {
219
+ name: 'k', ctrl: true, meta: false, shift: false,
220
+ handler: async () => {
221
+ cli.focus('util');
222
+ cli.write(`
223
+ Available keys:
224
+ • ALT-C: Clear screen
225
+ • ALT-R: Reset session
226
+ • ALT-I: Server info
227
+ • ALT-S: List/load sessions
228
+ • ALT-M: Copy last message
229
+ • CTRL-K: Show help
230
+ • CTRL-D: Exit
231
+ `.trim());
232
+ cli.focus('user', true);
233
+ }
234
+ }
235
+ ]);
236
+
237
+ // Input handler for user messages
238
+ cli.inputHandler = async (s) => {
239
+ if (busy || !s || !s.trim()) {
240
+ return;
241
+ }
242
+ await sendMessage({
243
+ action: 'user_request',
244
+ content: s.trim()
245
+ });
246
+ };
247
+
248
+ // Initialize
249
+
250
+ cli.focus('log');
251
+ cli.write('Connecting... (ALT-I for info, CTRL-K for keys, CTRL-D to exit)');
252
+
253
+ connect();
254
+
255
+ }
256
+