@j-o-r/hello-dave 0.0.6 → 0.0.7

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 (44) hide show
  1. package/CHANGELOG.md +14 -34
  2. package/README.md +240 -0
  3. package/README.md.bak +218 -0
  4. package/{examples → agents}/ask_agent.js +5 -5
  5. package/{examples → agents}/codeserver.sh +14 -14
  6. package/{examples → agents}/daisy_agent.js +5 -5
  7. package/{examples → agents}/docs_agent.js +5 -5
  8. package/{examples → agents}/gpt_agent.js +5 -5
  9. package/{examples → agents}/grok_agent.js +5 -5
  10. package/{examples → agents}/memory_agent.js +5 -5
  11. package/{examples → agents}/npm_agent.js +5 -5
  12. package/{examples → agents}/prompt_agent.js +5 -5
  13. package/agents/spawn_agent.js +137 -0
  14. package/{examples → agents}/test_agent.js +6 -6
  15. package/{examples → agents}/todo_agent.js +5 -5
  16. package/bin/codeDave +58 -0
  17. package/bin/dave.js +3 -5
  18. package/lib/AgentClient.js +111 -67
  19. package/lib/AgentManager.js +111 -80
  20. package/lib/AgentServer.js +144 -104
  21. package/lib/Cli.js +126 -93
  22. package/lib/Prompt.js +38 -5
  23. package/lib/Session.js +102 -79
  24. package/lib/ToolSet.js +79 -60
  25. package/lib/fafs.js +54 -19
  26. package/lib/genericToolset.js +109 -129
  27. package/lib/wsCli.js +50 -19
  28. package/lib/wsIO.js +10 -6
  29. package/package.json +3 -3
  30. package/types/AgentClient.d.ts +69 -35
  31. package/types/AgentManager.d.ts +50 -56
  32. package/types/AgentServer.d.ts +63 -16
  33. package/types/Cli.d.ts +56 -10
  34. package/types/Prompt.d.ts +36 -4
  35. package/types/Session.d.ts +23 -9
  36. package/types/ToolSet.d.ts +49 -32
  37. package/types/fafs.d.ts +68 -25
  38. package/types/wsCli.d.ts +14 -0
  39. package/types/wsIO.d.ts +9 -5
  40. package/utils/search_sessions.sh +100 -53
  41. package/bin/spawn_agent.js +0 -293
  42. package/lib/genericToolset.js.bak_syntax +0 -402
  43. /package/{examples → agents}/code_agent.js +0 -0
  44. /package/{examples → agents}/readme_agent.js +0 -0
@@ -14,46 +14,56 @@ const listSessionsSh = path.join(utilsDir, 'list_sessions.sh');
14
14
  const syntaxCheckSh = path.join(utilsDir, 'syntax_check.sh');
15
15
 
16
16
  const user = await env();
17
- const environment = `
17
+ const environment = `
18
18
  Name: ${user.name}
19
19
  System: ${user.system}
20
20
  City: ${user.city}
21
21
  Region: ${user.region}
22
22
  Country: ${user.country}
23
23
  Timezone: ${user.timezone}
24
- ExternalIp: ${user.external_ip}
24
+ ExternalIp: ${user.external_ip}
25
25
  `.trim();
26
26
  const tools = new ToolSet('auto');
27
27
 
28
28
  /**
29
- * reduce the error output to essential info only, if possible
30
- * @param {string} errorStr
31
- * @returns {string}
32
- */
29
+ * Reduces verbose JavaScript evaluation error output to essential info (line number + core message).
30
+ * @param {string} errorStr - Full Node.js error string.
31
+ * @returns {string} Simplified error.
32
+ * @private
33
+ */
33
34
  const getJSError = (errorStr) => {
34
35
  let result = '';
35
36
  const linematch = errorStr.match(/\[eval\]:(\d+)/);
36
37
  const lineNumber = linematch ? linematch[1] : '';
37
38
  const match = errorStr.split(/\" \"\[eval\]:\d+/s);
38
39
  if (match.length > 1) {
39
- // Remove last 10 lines
40
40
  const res = match[1].split('\n').slice(0, -10).join('\n');
41
- result = `Error: line ${lineNumber}\n${res} `
41
+ result = `Error: line ${lineNumber}\n${res} `;
42
42
  } else {
43
43
  result = errorStr;
44
44
  }
45
45
  return result;
46
- }
46
+ };
47
47
 
48
+ /**
49
+ * @module lib/genericToolset
50
+ * Secure utility tools for code execution, file I/O, system ops. Pre-populated ToolSet.
51
+ *
52
+ * @example
53
+ * import tools from './lib/genericToolset.js';
54
+ * await tools.call('get_user_env', {});
55
+ *
56
+ * @see {@link module:./index~ToolSet}
57
+ */
48
58
  tools.add(
49
59
  'javascript_interpreter',
50
- `Execute ESM ES6 javascript on \`node\`.`,
60
+ 'Execute ESM ES6 JavaScript on node.',
51
61
  {
52
62
  type: 'object',
53
63
  properties: {
54
64
  script: {
55
65
  type: 'string',
56
- description: `ES6 ESM Javascript eval. 'console.log' to capture the response. cwd: ${user.cwd}`,
66
+ description: `ES6 ESM code. Use console.log for output. cwd: ${user.cwd}`
57
67
  }
58
68
  },
59
69
  required: ['script']
@@ -67,32 +77,29 @@ ${params.script}
67
77
  ${delim}
68
78
  `.run();
69
79
  } catch (e) {
70
- const errorStr = e.toString();
71
- response = getJSError(errorStr);
80
+ response = getJSError(e.toString());
72
81
  }
73
82
  return response;
74
83
  }
75
84
  );
85
+
76
86
  tools.add(
77
- 'get_user_env', // name
78
- 'Get the user location, name and OS environment', // desciption
79
- {
80
- type: 'object',
81
- properties: {
82
- }
83
- },
84
- async (_params) => {
85
- return environment;
86
- }
87
+ 'get_user_env',
88
+ 'Get user environment info.',
89
+ { type: 'object', properties: {} },
90
+ async () => environment
87
91
  );
88
92
 
89
93
  tools.add(
90
94
  'execute_bash_script',
91
- 'Execute a bash script or command. (char escaping not needed)',
95
+ 'Execute raw bash script or command (no escaping needed).',
92
96
  {
93
97
  type: 'object',
94
98
  properties: {
95
- 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})` }
99
+ bash_script: {
100
+ type: 'string',
101
+ description: `Raw bash verbatim. Supports all syntax safely via heredoc. System: ${user.system}`
102
+ }
96
103
  },
97
104
  required: ['bash_script']
98
105
  },
@@ -101,19 +108,19 @@ tools.add(
101
108
  return await SH`bash <<'${delim}'
102
109
  ${params.bash_script}
103
110
  ${delim}
104
- `.run()
111
+ `.run();
105
112
  }
106
113
  );
107
114
 
108
115
  tools.add(
109
116
  'send_email',
110
- 'Send an email.',
117
+ 'Send email via msmtp.',
111
118
  {
112
119
  type: 'object',
113
120
  properties: {
114
- to: { type: 'string', description: 'Recipient email' },
121
+ to: { type: 'string', description: 'Recipient' },
115
122
  subject: { type: 'string', description: 'Subject' },
116
- body: { type: 'string', description: 'Message body' }
123
+ body: { type: 'string', description: 'Body' }
117
124
  },
118
125
  required: ['to', 'subject', 'body']
119
126
  },
@@ -128,193 +135,166 @@ ${delim}
128
135
  `.run();
129
136
  }
130
137
  );
138
+
131
139
  tools.add(
132
140
  'open_link',
133
- 'Open an url or file in the local user environment. (xdg-open)',
141
+ 'Open URL/file with xdg-open.',
134
142
  {
135
143
  type: 'object',
136
144
  properties: {
137
- url: { type: 'string', description: 'file | URL' }
145
+ url: { type: 'string', description: 'URL or file path' }
138
146
  },
139
147
  required: ['url']
140
148
  },
141
- async (params) => {
142
- return await SH`xdg-open ${[params.url]}`.run();
143
- }
149
+ async (params) => await SH`xdg-open ${[params.url]}`.run()
144
150
  );
151
+
145
152
  tools.add(
146
153
  'execute_remote_script',
147
- 'Execute bash script on a remote machine via SSH.',
154
+ 'Run bash on remote via SSH.',
148
155
  {
149
156
  type: 'object',
150
157
  properties: {
151
- url: { type: 'string', description: 'SSH URL, e.g., ssh://user@host or ssh://user@host:port' },
152
- script: { type: 'string', description: 'RAW script code to execute remotely (no escaping needed)' }
158
+ url: { type: 'string', description: 'ssh://user@host[:port]' },
159
+ script: { type: 'string', description: 'Raw script' }
153
160
  },
154
161
  required: ['url', 'script']
155
162
  },
156
163
  async (params) => {
157
164
  const { url, script } = params;
158
- if (!url.startsWith('ssh://')) throw new Error('Invalid SSH URL');
165
+ if (!url.startsWith('ssh://')) throw new Error('ssh://user@host[:port]');
159
166
  const withoutProto = url.slice(6);
160
167
  const parts = withoutProto.split(':');
161
- let port = 22;
162
- let userHost = withoutProto;
163
- if (parts.length > 1) {
164
- userHost = parts[0];
165
- port = parseInt(parts[1]);
166
- }
168
+ let port = 22, userHost = withoutProto;
169
+ if (parts.length > 1) { userHost = parts[0]; port = parseInt(parts[1]); }
167
170
  const [user, host] = userHost.split('@');
168
- if (!user || !host) throw new Error('Invalid SSH URL format: use ssh://user@host[:port]');
171
+ if (!user || !host) throw new Error('Invalid SSH URL');
169
172
 
170
173
  const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
171
174
  return await SH`ssh -p ${port} ${user}@${host} bash <<'${delim}'
172
175
  ${script}
173
176
  ${delim}
174
177
  `.run();
175
-
176
178
  }
177
179
  );
180
+
178
181
  tools.add(
179
182
  'history_search',
180
- `Search previous LLM chat sessions or list them hierarchically.
181
- Example query: "(todo|task)" or "package.json".
182
- Searches filenames & content in .cache/[app]/[prompt]/sessions/*.ndjson (case-insensitive regex, with context).
183
- Omit query (or use empty string) to list sessions (see utils/list_sessions.sh).`,
183
+ 'Search/list chat sessions in .cache/.',
184
184
  {
185
185
  type: 'object',
186
186
  properties: {
187
- query: {
188
- type: 'string',
189
- description: `Search query or regex (quoted for multi-word/regex). Omit or empty for list mode.`
190
- }
187
+ query: { type: 'string', description: 'Query/regex or empty to list' }
191
188
  },
192
189
  required: []
193
190
  },
194
191
  async (params) => {
195
- if (typeof params.query === 'string' && params.query.trim() !== '') {
196
- const escapedQuery = bashEscape(params.query);
197
- return await SH`${searchSessionsSh} "${escapedQuery}"`.run();
198
- } else {
199
- return await SH`${listSessionsSh}`.run();
192
+ if (typeof params.query === 'string' && params.query.trim()) {
193
+ return await SH`${searchSessionsSh} "${bashEscape(params.query)}"`.run();
200
194
  }
195
+ return await SH`${listSessionsSh}`.run();
201
196
  }
202
197
  );
198
+
203
199
  tools.add(
204
200
  'read_file',
205
- 'Read the raw content of a file strictly within the current working directory (CWD). Paths must be relative (no leading /, no ..).',
201
+ 'Read file from CWD (relative path only).',
206
202
  {
207
203
  type: 'object',
208
204
  properties: {
209
- file: {
210
- type: 'string',
211
- description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed). cwd: ${user.cwd}`
212
- }
205
+ file: { type: 'string', description: `Relative path. cwd: ${user.cwd}` }
213
206
  },
214
207
  required: ['file']
215
208
  },
216
209
  async (params) => {
217
210
  const file = params.file?.trim();
218
- if (typeof file !== 'string' || !file) {
219
- throw new Error('Valid relative file path required.');
220
- }
221
- if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
222
- throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
223
- }
224
- const resolvedPath = path.resolve(process.cwd(), file);
225
- if (!resolvedPath.startsWith(process.cwd())) {
226
- throw new Error(`Path '${file}' escapes CWD scope.`);
227
- }
228
- try {
229
- const content = await fs.readFile(resolvedPath, 'utf8');
230
- return content;
231
- } catch (e) {
232
- throw new Error(`Failed to read '${file}': ${e.message}`);
211
+ if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
212
+ throw new Error('Relative CWD path only (no /, .., \\\\).');
233
213
  }
214
+ const resolved = path.resolve(process.cwd(), file);
215
+ if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
216
+ return await fs.readFile(resolved, 'utf8');
234
217
  }
235
218
  );
236
219
 
237
220
  tools.add(
238
221
  'write_file',
239
- '**MANDATORY**: 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 in params**). **AUTO-VALIDATES** JS/Python/Bash/JSON/etc. via `utils/syntax_check.sh`; chmod +x shebangs. Retries syntax errors force LLM fix. **Always use write_file for safety (CWD-only, syntax-checked).**',
222
+ 'Write/validate file in CWD. Auto-syntax check + JS fix + chmod.',
240
223
  {
241
224
  type: 'object',
242
225
  properties: {
243
- file: {
244
- type: 'string',
245
- description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed). cwd: ${user.cwd}`
246
- },
247
- content: {
248
- type: 'string',
249
- description: "Raw content to write (LITERAL: NO char escaping needed in this param; supports newlines, $, |, <, >, &, \", ', \\, \` , etc. verbatim). **Generate valid syntax for target lang** (e.g., JS template literals use raw `${var}`, no invalid \\`; syntax_check.sh rejects bad syntax like escaped backticks in JS)."
250
- }
226
+ file: { type: 'string', description: `Relative path. cwd: ${user.cwd}` },
227
+ content: { type: 'string', description: 'Raw content verbatim (no escaping).' }
251
228
  },
252
229
  required: ['file', 'content']
253
230
  },
254
231
  async (params) => {
255
232
  const file = params.file?.trim();
256
233
  const content = params.content ?? '';
257
- if (typeof file !== 'string' || !file) {
258
- throw new Error('Valid relative file path required.');
259
- }
260
- if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
261
- throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
262
- }
263
- const resolvedPath = path.resolve(process.cwd(), file);
264
- if (!resolvedPath.startsWith(process.cwd())) {
265
- throw new Error(`Path '${file}' escapes CWD scope.`);
234
+ if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
235
+ throw new Error('Relative CWD path only.');
266
236
  }
267
- try {
268
- await fs.writeFile(resolvedPath, content, 'utf8');
237
+ const resolved = path.resolve(process.cwd(), file);
238
+ if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
239
+ const dir = path.dirname(resolved);
240
+ const ext = path.extname(file);
241
+ let finalContent = content, validationMsg = '';
269
242
 
270
- // AUTO-VALIDATE: Multi-lang syntax check via utils/syntax_check.sh + chmod shebang
271
- const hasShebang = content.startsWith('#!');
243
+ const temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2,6)}${ext || '.tmp'}`);
244
+ await fs.writeFile(temp1, content, 'utf8');
272
245
 
273
- let validationMsg = '';
274
- try {
275
- await SH`${syntaxCheckSh} ${[resolvedPath]}`.run();
276
- validationMsg = ' ✓ Multi-lang syntax OK';
277
- } catch (e) {
278
- const errPreview = content.slice(0, 1000).split('\n').slice(0, 20).join('\n');
279
- 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.`);
246
+ try {
247
+ await SH`${syntaxCheckSh} ${[temp1]}`.run();
248
+ validationMsg = ' ✓ Syntax OK';
249
+ } catch (e) {
250
+ if (!['.js','.mjs'].includes(ext)) {
251
+ await fs.unlink(temp1).catch(()=>{});
252
+ throw new Error(`Syntax error: ${e.message}`);
280
253
  }
281
- if (hasShebang) {
282
- await SH`chmod +x ${[resolvedPath]}`.run();
283
- validationMsg += ' ✓ chmod +x';
254
+ let fixed = content.replace(/\\\\`/g, '\\`').replace(/\\\`/g, '`').replace(/\\`/g, '`').replace(/\\\$/g, '$').replace(/\\\${/g, '${');
255
+ const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2,6)}.js`);
256
+ await fs.writeFile(temp2, fixed, 'utf8');
257
+ try {
258
+ await SH`${syntaxCheckSh} ${[temp2]}`.run();
259
+ finalContent = fixed;
260
+ await fs.rename(temp2, resolved);
261
+ await fs.unlink(temp1).catch(()=>{});
262
+ validationMsg = ' ✓ Fixed JS';
263
+ } catch {
264
+ await fs.unlink(temp1).catch(()=>{});
265
+ await fs.unlink(temp2).catch(()=>{});
266
+ throw new Error(`Syntax error (fix failed): ${e.message}`);
284
267
  }
268
+ }
285
269
 
286
- return `Successfully wrote to '${file}' (${Buffer.byteLength(content, 'utf8')} bytes).${validationMsg}`;
287
- } catch (e) {
288
- throw new Error(`Failed to write '${file}': ${e.message}`);
270
+ if (validationMsg === ' ✓ Syntax OK') await fs.rename(temp1, resolved);
271
+
272
+ if (finalContent.startsWith('#!')) {
273
+ await SH`chmod +x ${[resolved]}`.run();
274
+ validationMsg += ' ✓ +x';
289
275
  }
276
+
277
+ return `Wrote ${file} (${Buffer.byteLength(finalContent, 'utf8')} bytes).${validationMsg}`;
290
278
  }
291
279
  );
292
280
 
293
281
  tools.add(
294
282
  'syntax_check',
295
- 'Standalone syntax validation for files (JS/Python/Bash/JSON/etc.) via utils/syntax_check.sh. Detects lang from ext/shebang.',
283
+ 'Syntax validate file via utils/syntax_check.sh.',
296
284
  {
297
285
  type: 'object',
298
286
  properties: {
299
- file: {
300
- type: 'string',
301
- description: `Relative path to the file within CWD.`
302
- }
287
+ file: { type: 'string', description: 'Relative CWD path' }
303
288
  },
304
289
  required: ['file']
305
290
  },
306
291
  async (params) => {
307
292
  const file = params.file?.trim();
308
- if (typeof file !== 'string' || !file) {
309
- throw new Error('Valid relative file path required.');
310
- }
311
- const resolvedPath = path.resolve(process.cwd(), file);
312
- if (!resolvedPath.startsWith(process.cwd())) {
313
- throw new Error(`Path '${file}' escapes CWD scope.`);
314
- }
315
- return await SH`${syntaxCheckSh} ${[resolvedPath]}`.run();
293
+ if (typeof file !== 'string' || !file) throw new Error('Relative path required.');
294
+ const resolved = path.resolve(process.cwd(), file);
295
+ if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
296
+ return await SH`${syntaxCheckSh} ${[resolved]}`.run();
316
297
  }
317
298
  );
318
299
 
319
-
320
- export default tools
300
+ export default tools;
package/lib/wsCli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env -S node
2
- /*
3
- * WebSocket client for a hello-dave server with auto-reconnect and improved structure.
4
- * user CLI
2
+ /**
3
+ * @fileoverview Interactive WebSocket CLI client for hello-dave agent servers.
4
+ * Features: auto-reconnect, keyboard shortcuts (ALT-C/R/I/S/M, CTRL-K),
5
+ * session management, message history, clipboard copy.
6
+ *
7
+ * Depends on @j-o-r/cli for terminal UI, @j-o-r/apiserver WebSocketClient, @j-o-r/sh for shell.
5
8
  */
6
9
  import cli from '@j-o-r/cli';
7
10
  import { WebSocketClient } from "@j-o-r/apiserver";
@@ -9,11 +12,17 @@ import { SH } from '@j-o-r/sh';
9
12
 
10
13
  const OPEN = 1; // WebSocket.OPEN
11
14
 
15
+ /**
16
+ * @typedef {Object} WsMessage
17
+ * @property {string} action - Action type (e.g., 'user_request')
18
+ * @property {string} content - Message content
19
+ * @property {number} id - Unique message ID
20
+ */
12
21
 
13
22
  /**
14
23
  * Copy text to the clipboard using xclip.
15
- * @param {string} text
16
- * @returns {Promise<void>}
24
+ * @param {string} text - Text to copy
25
+ * @returns {Promise&lt;void&gt;}
17
26
  */
18
27
  const copyToClipboard = async (text) => {
19
28
  if (typeof text !== 'string') return;
@@ -21,11 +30,28 @@ const copyToClipboard = async (text) => {
21
30
  const prams = ['-selection', 'clipboard'];
22
31
  await SH`xclip ${prams}`.options({ stdio: 'inherit' }).run(text);
23
32
  };
33
+
24
34
  /**
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
- */
35
+ * Launches an interactive CLI client connected to a hello-dave agent server via WebSocket.
36
+ * Establishes persistent connection with auto-reconnect, handles user input,
37
+ * keyboard shortcuts for common actions, and displays responses.
38
+ *
39
+ * Keyboard shortcuts:
40
+ * - ALT-C: Clear screen
41
+ * - ALT-R: Reset session
42
+ * - ALT-S: List/load sessions
43
+ * - ALT-I: Server info
44
+ * - ALT-M: Copy last message to clipboard
45
+ * - CTRL-K: Show help
46
+ * - CTRL-D: Exit (standard)
47
+ *
48
+ * @param {string} connectUrl - WebSocket server endpoint (e.g., 'ws://localhost:8080')
49
+ * @param {string} [secret=''] - Optional base64 secret for authenticated connections
50
+ * @returns {void}
51
+ * @example
52
+ * import wsCli from './lib/wsCli.js';
53
+ * wsCli('ws://localhost:8080', 'mysecret');
54
+ */
29
55
  export default (connectUrl, secret = '') => {
30
56
  let ws;
31
57
  let busy = false;
@@ -36,8 +62,12 @@ export default (connectUrl, secret = '') => {
36
62
  }
37
63
 
38
64
  /**
39
- * Connect to the WebSocket server (with handlers).
40
- * Sets up reconnection on close.
65
+ * Connects (or reconnects) to the WebSocket server.
66
+ * Sets up event handlers for messages, close (auto-reconnect), errors.
67
+ * Sends user_introduction on open.
68
+ *
69
+ * @private
70
+ * @returns {void}
41
71
  */
42
72
  const connect = () => {
43
73
  if (ws && ws.readyState === OPEN) {
@@ -94,10 +124,14 @@ export default (connectUrl, secret = '') => {
94
124
  };
95
125
 
96
126
  /**
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
127
+ * Sends a message and awaits response by ID.
128
+ * Handles special response actions (e.g., server_response updates UI).
129
+ * Manages busy state and spinner.
130
+ *
131
+ * @private
132
+ * @param {WsMessage} message - Message to send (id auto-added)
133
+ * @returns {Promise&lt;WsMessage&gt;} Response data
134
+ * @throws {Error} If not connected, timeout (12h), or connection error
101
135
  */
102
136
  const sendMessage = async (message) => {
103
137
  if (!ws || ws.readyState !== OPEN) {
@@ -246,11 +280,8 @@ Available keys:
246
280
  };
247
281
 
248
282
  // Initialize
249
-
250
283
  cli.focus('log');
251
284
  cli.write('Connecting... (ALT-I for info, CTRL-K for keys, CTRL-D to exit)');
252
285
 
253
286
  connect();
254
-
255
- }
256
-
287
+ };
package/lib/wsIO.js CHANGED
@@ -14,17 +14,21 @@ import { WebSocket } from 'ws';
14
14
  * Sends intro + action, awaits matching response by ID, closes, returns response.
15
15
  *
16
16
  * @param {string} connectUrl - Websocket server endpoint to connect to
17
- * @param {string} [secret] - Secret websocket connection key
18
- * @param {'user_request'|'user_info'|'user_reset'} action - Action
19
- * @param {string} [input] - When action is 'user_request' input is the query
20
- * @returns {Promise<wsResponse>}
17
+ * @param {string} [secret=''] - Secret websocket connection key
18
+ * @param {'user_request'|'user_info'|'user_reset'} action - Action to perform
19
+ * @param {string} [input=''] - Input content (required for 'user_request')
20
+ * @returns {Promise&lt;wsResponse&gt;}
21
+ * @throws {Error} Invalid action or missing input for user_request
22
+ * @example
23
+ * const response = await wsio('ws://localhost:8080', 'secret', 'user_request', 'Hello!');
24
+ * console.log(response.content);
21
25
  */
22
26
  export default async function wsio(connectUrl, secret = '', action, input = '') {
23
27
  if (!['user_request', 'user_reset', 'user_info'].includes(action)) {
24
- throw new Error(`Invalid action: ${action}. Must be one of: user_input, user_reset, user_info`);
28
+ throw new Error(`Invalid action: ${action}. Must be one of: user_request, user_reset, user_info`);
25
29
  }
26
30
  if (action === 'user_request' && (!input || typeof input !== 'string' || input.trim() === '')) {
27
- throw new Error('Non-empty string input required for "user_input"');
31
+ throw new Error('Non-empty string input required for "user_request"');
28
32
  }
29
33
 
30
34
  let b64secret = '';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@j-o-r/hello-dave",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.7",
5
5
  "description": "ESM toolkit for building AI agents with unified access to Grok (XAI), OpenAI, and Anthropic endpoints",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "bin": {
16
16
  "dave": "bin/dave.js",
17
- "createAgent": "bin/spawn_agent.js"
17
+ "codeDave": "bin/codeDave"
18
18
  },
19
19
  "scripts": {
20
20
  "release": "npm run types && npm pack --pack-destination=release",
@@ -51,4 +51,4 @@
51
51
  "engines": {
52
52
  "node": ">=20"
53
53
  }
54
- }
54
+ }