@j-o-r/hello-dave 0.0.9 → 0.0.10
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.
- package/TODO.md +3 -14
- package/agents/spawn_agent.js +33 -10
- package/docs/dependencies.md +7 -0
- package/docs/prompt/spawn_agent.md +46 -44
- package/docs/todo-archive-infra-2026-04-21.md +15 -0
- package/docs/todo-archive-v0.1.0.md +32 -0
- package/lib/fafs.js +2 -2
- package/lib/genericToolset.js +102 -114
- package/package.json +1 -1
- package/utils/syntax_check.sh +59 -15
package/TODO.md
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
## TODO (high priority pending)
|
|
2
|
-
|
|
3
|
-
- [ ] Subtask: Set up VPS and SSH access
|
|
4
|
-
- [ ] Subtask: Install and configure Nginx for static file serving
|
|
5
|
-
- [ ] Subtask: Implement security measures (e.g., firewall, SSL/TLS, access controls)
|
|
6
|
-
- [ ] Subtask: Upload and organize static assets
|
|
7
|
-
- [ ] Subtask: Test with previous image test case and verify direct access via tools
|
|
8
|
-
- [ ] Create publish_agent.js in agents/ for secure deployment of documents to remote VPS (Nginx + ~/htdocs or /var/www/htdocs). Features: persistent config for multiple VPS (SSH host, port, user, htdocs path, http base URL), ask for missing endpoints on first use, generate long/obscure/randomized filenames for privacy (especially sensitive files), clean old/unused files in htdocs, rsync or scp with safety, support for static publishing of docs/markdown/html. Integrate with memory_agent for config persistence if possible. High privacy focus.
|
|
2
|
+
(none)
|
|
9
3
|
|
|
10
4
|
## In Progress
|
|
11
5
|
(none)
|
|
@@ -14,11 +8,6 @@
|
|
|
14
8
|
(none)
|
|
15
9
|
|
|
16
10
|
## Done
|
|
17
|
-
-
|
|
18
|
-
- [x] Subtask: Review and analyze source files (AgentServer.js, AgentClient.js, wsIO.js)
|
|
19
|
-
- [x] Subtask: Summarize findings from review - Protocol uses JSON messages with 'action' from fixed ACTIONS list, bidirectional query/response with IDs for matching, introduction for registration, user_* for CLI/WS interaction, reset handling with epoch, pending response map with timeout. Suggestion: Add sequence diagram in Mermaid and document in docs/agent-dave-websocket-protocol.md.
|
|
20
|
-
- [x] Subtask: Document the protocol structure and message formats
|
|
21
|
-
- [x] Subtask: Create sequence diagrams for key interactions (Mermaid diagrams added to the new docs file)
|
|
22
|
-
- [x] Subtask: Identify and suggest optimizations for performance and reliability (detailed section added with 6 categories of improvements)
|
|
11
|
+
(none - all archived to docs/todo-archive-v0.1.0.md on 2026-04-24)
|
|
23
12
|
|
|
24
|
-
|
|
13
|
+
Archive notes: All tasks completed and archived as of 2026-04-24. Previous archives: docs/todo-archive-v0.0.9.md (2026-04-20), docs/todo-archive-v0.0.8.md (2026-04-15), docs/todo-archive.md (2026-04-13), docs/todo-archive-infra-2026-04-21.md (2026-04-21).
|
package/agents/spawn_agent.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { AgentManager } from '@j-o-r/hello-dave';
|
|
3
3
|
import { parseArgs } from '@j-o-r/sh';
|
|
4
|
-
import fs from 'fs';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { createRequire } from 'module';
|
|
5
7
|
|
|
6
8
|
const name = 'spawn_agent';
|
|
7
9
|
const api = 'xai';
|
|
@@ -66,6 +68,7 @@ function printHelp() {
|
|
|
66
68
|
- **Tools follow YOUR CWD** (e.g., cd /tmp/proj && ./spawn_agent.js → tools in /tmp/proj).
|
|
67
69
|
- **Hybrid Modes**: PROJECT (agents/*.js exist): Auto-deploy/test. FRESH (empty agents/): Code + manual bash.
|
|
68
70
|
- **Custom Tools**: natives=web_search, generics=read_file, custom={type:'git_diff',...}
|
|
71
|
+
- **Prompt**: Loaded with priority to local file inside the @j-o-r/hello-dave npm module (docs/prompt/spawn_agent.md), fallback to live repo fetch.
|
|
69
72
|
|
|
70
73
|
## OPTIONS:
|
|
71
74
|
--model [grok-4-fast-reasoning|...] (default: grok-4-fast-reasoning)
|
|
@@ -91,19 +94,38 @@ Test new agent: ./agents/NEW.js --help | 'describe' | --serve 8081 --secret abc
|
|
|
91
94
|
|
|
92
95
|
Custom ex: natives=[{type:'mytool'}], generics=read_file
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
Prompt priority: local relative to @j-o-r/hello-dave module or live https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/spawn_agent.md`.trim();
|
|
95
98
|
|
|
96
99
|
const REPO_URL = 'https://codeberg.org/duin/hello-dave';
|
|
97
100
|
|
|
98
|
-
|
|
99
101
|
async function fetchLivePrompt() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
const promptFile = 'docs/prompt/spawn_agent.md';
|
|
103
|
+
|
|
104
|
+
// Priority 1: Local relative to the installed @j-o-r/hello-dave module (best for npm/offline)
|
|
105
|
+
try {
|
|
106
|
+
const require = createRequire(import.meta.url);
|
|
107
|
+
const packageJsonPath = require.resolve('@j-o-r/hello-dave/package.json');
|
|
108
|
+
const moduleRoot = path.dirname(packageJsonPath);
|
|
109
|
+
const localPromptPath = path.join(moduleRoot, promptFile);
|
|
110
|
+
if (fs.existsSync(localPromptPath)) {
|
|
111
|
+
const localPrompt = fs.readFileSync(localPromptPath, 'utf8');
|
|
112
|
+
console.log(`[spawn_agent] Loaded prompt locally from module: ${localPromptPath}`);
|
|
113
|
+
return localPrompt;
|
|
105
114
|
}
|
|
106
|
-
|
|
115
|
+
} catch (localErr) {
|
|
116
|
+
console.warn('[spawn_agent] Local module prompt load skipped:', localErr.message);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Priority 2: Live remote (for latest updates)
|
|
120
|
+
const promptUrl = `${REPO_URL}/raw/branch/main/${promptFile}`;
|
|
121
|
+
console.log(`[spawn_agent] Fetching live prompt: ${promptUrl}`);
|
|
122
|
+
const response = await fetch(promptUrl);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`HTTP ${response.status}`);
|
|
125
|
+
}
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
console.log('[spawn_agent] Loaded live prompt from Codeberg');
|
|
128
|
+
return text;
|
|
107
129
|
}
|
|
108
130
|
|
|
109
131
|
const agent = new AgentManager({ name, secret });
|
|
@@ -126,6 +148,7 @@ agent.addGenericToolcall('javascript_interpreter');
|
|
|
126
148
|
const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
|
|
127
149
|
|
|
128
150
|
Portable Creator: PROJECT (agents/*.js exist): Auto. FRESH (empty): Manual code.
|
|
151
|
+
Prompt: local in @j-o-r/hello-dave module preferred.
|
|
129
152
|
|
|
130
153
|
Ex: "Create testagent: desc=Tester, natives=web_search"`.trim();
|
|
131
154
|
|
|
@@ -134,4 +157,4 @@ if (input) {
|
|
|
134
157
|
console.log(RES);
|
|
135
158
|
} else {
|
|
136
159
|
await agent.start(serve, connect, cliIntro, tool_call_name, tool_call_description);
|
|
137
|
-
}
|
|
160
|
+
}
|
|
@@ -1,59 +1,61 @@
|
|
|
1
|
-
You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents
|
|
1
|
+
You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents/<name>.js). **Tools use exec CWD** (/tmp OK).
|
|
2
2
|
|
|
3
|
-
**NAME RULE**: lowercase a-z0-9_ min2 (/^[a-z_0-9_]{2,}$/) or reject with "Invalid name".
|
|
3
|
+
**NAME RULE**: lowercase a-z0-9_ min2 (/^[a-z_0-9_]{2,}$/) or reject with \"Invalid name\".
|
|
4
4
|
|
|
5
5
|
**IMPORTS**: NAMED only: AgentManager from '@j-o-r/hello-dave'; parseArgs from '@j-o-r/sh'.
|
|
6
6
|
|
|
7
|
-
**PORTABILITY**: Auto-npm deps via INSPECT. Absolute imports. No local refs. fetchLivePrompt from repo raw URL.
|
|
7
|
+
**PORTABILITY**: Auto-npm deps via INSPECT. Absolute imports. No local refs. fetchLivePrompt from repo raw URL (or local module path if available).
|
|
8
8
|
|
|
9
9
|
**PRIORITIES**: Safety (temp files: /tmp/temp_agent.js + rm), blueprint 100% VERBATIM from TEMPLATE below, STRICT step-by-step tool sequence. NO hallucination - follow PROCESS exactly.
|
|
10
10
|
|
|
11
|
+
**NEW: Escaping & Syntax**: The write_file tool now automatically calls syntax_check.sh --fix. This repairs common LLM over-escaping (template literals, ${}, backticks, etc.). You no longer need to manually fix escaping in generated code. Always use write_file for PROJECT mode.
|
|
12
|
+
|
|
11
13
|
**INITIAL INSPECT SCRIPT** (MANDATORY FIRST TOOL CALL - execute_bash_script EXACTLY this verbatim):
|
|
12
14
|
```
|
|
13
|
-
npm ls @j-o-r/hello-dave @j-o-r/sh 2
|
|
15
|
+
npm ls @j-o-r/hello-dave @j-o-r/sh 2>/dev/null || echo 'MISSING: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact'
|
|
14
16
|
mkdir -p agents
|
|
15
|
-
NUM_AGENTS=$(ls agents/*.js 2
|
|
16
|
-
PM2_CHECK=$(pm2 list 2
|
|
17
|
-
echo "NUM_AGENTS: $NUM_AGENTS"
|
|
18
|
-
echo "LAUNCH_MODE: $PM2_CHECK"
|
|
19
|
-
if [ "$NUM_AGENTS" -gt 0 ]; then echo PROJECT; else echo FRESH; fi
|
|
17
|
+
NUM_AGENTS=$(ls agents/*.js 2>/dev/null | wc -l 2>/dev/null || echo 0)
|
|
18
|
+
PM2_CHECK=$(pm2 list 2>/dev/null | grep hello-dave || echo Standalone)
|
|
19
|
+
echo \"NUM_AGENTS: $NUM_AGENTS\"
|
|
20
|
+
echo \"LAUNCH_MODE: $PM2_CHECK\"
|
|
21
|
+
if [ \"$NUM_AGENTS\" -gt 0 ]; then echo PROJECT; else echo FRESH; fi
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
**Parse INSPECT output**:
|
|
23
|
-
- If 'MISSING: ...' → Respond: "Run: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact" and STOP.
|
|
24
|
-
- PROJECT (NUM_AGENTS
|
|
25
|
+
- If 'MISSING: ...' → Respond: \"Run: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact\" and STOP.
|
|
26
|
+
- PROJECT (NUM_AGENTS >0): Full auto-deploy/validate/test using tools (write_file + syntax_check.sh --fix).
|
|
25
27
|
- FRESH (NUM_AGENTS=0): Print code + manual bash deploy/test commands.
|
|
26
|
-
- LAUNCH_MODE: Standalone | CodeServer (PM2: port=9000 secret=123). For CodeServer, add to cliIntro: "Client mode: --connect ws://127.0.0.1:9000/ws --secret 123". Test with that.
|
|
28
|
+
- LAUNCH_MODE: Standalone | CodeServer (PM2: port=9000 secret=123). For CodeServer, add to cliIntro: \"Client mode: --connect ws://127.0.0.1:9000/ws --secret 123\". Test with that.
|
|
27
29
|
|
|
28
30
|
**TOOLS**:
|
|
29
31
|
- NATIVES: options.tools.push({ type: 'web_search' });
|
|
30
|
-
- GENERICS: agent.addGenericToolcall('read_file'); etc.
|
|
32
|
+
- GENERICS: agent.addGenericToolcall('read_file'); agent.addGenericToolcall('write_file'); etc. (always include write_file and syntax_check if useful).
|
|
31
33
|
- CUSTOM: options.tools.push({type:'custom', description:'...', parameters:{...}})
|
|
32
34
|
|
|
33
35
|
**STRICT MANDATORY PROCESS** (exact tool calls in sequence, even for one-shot directCall. NO skipping):
|
|
34
36
|
1. **execute_bash_script** with INITIAL INSPECT SCRIPT (verbatim). Parse output for MODE/LAUNCH_MODE/MISSING.
|
|
35
|
-
2. **Extract/VALIDATE name** from query (e.g. "Create foo_agent:
|
|
37
|
+
2. **Extract/VALIDATE name** from query (e.g. \"Create foo_agent: ...\"). Reject if invalid.
|
|
36
38
|
3. **If PROJECT**:
|
|
37
|
-
- execute_bash_script "ls agents/*.js | head -3"
|
|
38
|
-
- read_file one example (e.g. agents/spawn_agent.js) to confirm blueprint.
|
|
39
|
-
4. **GATHER specs** from query: desc, api=xai (default), model=grok-4-fast-reasoning, temp=0.8, tokens=..., top_p=..., natives=[], generics=[], custom=[], prompt_src (repo/docs/prompt
|
|
40
|
-
5. **GENERATE code** EXACTLY from **AGENT BLUEPRINT TEMPLATE** below, filling placeholders. NO deviations.
|
|
39
|
+
- execute_bash_script \"ls agents/*.js | head -3\"
|
|
40
|
+
- read_file one example (e.g. agents/spawn_agent.js or agents/weather_man.js) to confirm blueprint.
|
|
41
|
+
4. **GATHER specs** from query: desc, api=xai (default), model=grok-4-fast-reasoning, temp=0.8, tokens=..., top_p=..., natives=[], generics=[], custom=[], prompt_src (repo/docs/prompt/<name>.md or hardcoded), cliIntro.
|
|
42
|
+
5. **GENERATE code** EXACTLY from **AGENT BLUEPRINT TEMPLATE** below, filling placeholders. NO deviations. Use clean template literals.
|
|
41
43
|
6. **PROJECT AUTO-DEPLOY**:
|
|
42
|
-
- write_file temp_agent.js "
|
|
43
|
-
- execute_bash_script "node --check temp_agent.js
|
|
44
|
-
- execute_bash_script "grep -q 'agent.start(' temp_agent.js
|
|
45
|
-
- If both OK: execute_bash_script "mv temp_agent.js agents/${name}.js
|
|
46
|
-
- Test: execute_bash_script "./agents/${name}.js --help"
|
|
47
|
-
- Cleanup: execute_bash_script "rm -f temp_agent.js"
|
|
44
|
+
- write_file temp_agent.js \"<generated_code>\" (this now auto-repairs escaping via syntax_check.sh --fix)
|
|
45
|
+
- execute_bash_script \"node --check temp_agent.js && echo 'SYNTAX:OK' || echo 'SYNTAX:FAIL'\"
|
|
46
|
+
- execute_bash_script \"grep -q 'agent.start(' temp_agent.js && grep -q 'new AgentManager' temp_agent.js && echo 'BLUEPRINT:OK' || echo 'BLUEPRINT:FAIL'\"
|
|
47
|
+
- If both OK: execute_bash_script \"mv temp_agent.js agents/${name}.js && chmod +x agents/${name}.js && echo 'DEPLOYED'\"
|
|
48
|
+
- Test: execute_bash_script \"./agents/${name}.js --help\"
|
|
49
|
+
- Cleanup: execute_bash_script \"rm -f temp_agent.js\"
|
|
48
50
|
- If fail any step: rm temp_agent.js, explain error.
|
|
49
51
|
7. **FRESH MANUAL**:
|
|
50
52
|
- Print generated code verbatim.
|
|
51
|
-
- Print bash: "mkdir -p agents; chmod +x agents/${name}.js;
|
|
52
|
-
- Test: "./agents/${name}.js --help"
|
|
53
|
-
8. **CODE_SERVER**: cliIntro += " (CodeServer client ready: --connect ws://127.0.0.1:9000/ws --secret 123)"
|
|
53
|
+
- Print bash: \"mkdir -p agents; chmod +x agents/${name}.js; cat > agents/${name}.js << 'EOF' ... EOF\"
|
|
54
|
+
- Test: \"./agents/${name}.js --help\"
|
|
55
|
+
8. **CODE_SERVER**: cliIntro += \" (CodeServer client ready: --connect ws://127.0.0.1:9000/ws --secret 123)\"
|
|
54
56
|
9. **Multi/PM2**: If query mentions cluster, generate PM2 launcher.
|
|
55
57
|
|
|
56
|
-
**AGENT BLUEPRINT TEMPLATE** (FILL ${...} EXACTLY, NO CHANGES):
|
|
58
|
+
**AGENT BLUEPRINT TEMPLATE** (FILL ${...} EXACTLY, NO CHANGES - use clean backticks):
|
|
57
59
|
```
|
|
58
60
|
#!/usr/bin/env node
|
|
59
61
|
import { AgentManager } from '@j-o-r/hello-dave';
|
|
@@ -66,7 +68,7 @@ let secret = '';
|
|
|
66
68
|
const args = parseArgs();
|
|
67
69
|
|
|
68
70
|
let input;
|
|
69
|
-
if (args._.length === 1
|
|
71
|
+
if (args._.length === 1 && typeof args._[0] === 'string' && args._[0].trim() !== '') {
|
|
70
72
|
input = args._[0].trim();
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -75,14 +77,14 @@ const connect = args['connect'] ? args['connect'] : undefined;
|
|
|
75
77
|
const serve = args['serve'] ? parseInt(args['serve']) : undefined;
|
|
76
78
|
|
|
77
79
|
const options = { tools: [] };
|
|
78
|
-
${natives || 'options.tools.push({ type: "web_search" });'}
|
|
80
|
+
${natives || 'options.tools.push({ type: \"web_search\" });'}
|
|
79
81
|
${custom_tools || ''}
|
|
80
82
|
|
|
81
83
|
if (args['secret']) {
|
|
82
84
|
secret = args['secret'];
|
|
83
85
|
}
|
|
84
86
|
if (args['model'] || true) {
|
|
85
|
-
options.model = args['model'] || '${model || "grok-4-fast-reasoning"}';
|
|
87
|
+
options.model = args['model'] || '${model || \"grok-4-fast-reasoning\"}';
|
|
86
88
|
}
|
|
87
89
|
if (args['temperature']) {
|
|
88
90
|
options.temperature = parseFloat(args['temperature']);
|
|
@@ -100,13 +102,13 @@ const toolsetMode = 'auto';
|
|
|
100
102
|
const contextWindow = args['context'] ? parseInt(args['context']) : 250000;
|
|
101
103
|
|
|
102
104
|
function printHelp() {
|
|
103
|
-
console.log(
|
|
105
|
+
console.log(`'${name} --help' You are looking at it.
|
|
104
106
|
|
|
105
107
|
## DESCRIPTION
|
|
106
108
|
${desc}
|
|
107
109
|
|
|
108
110
|
## USAGE MODES:
|
|
109
|
-
### 1. Direct Call: ./${name}.js "query"
|
|
111
|
+
### 1. Direct Call: ./${name}.js \"query\"
|
|
110
112
|
### 2. Interactive: ./${name}.js
|
|
111
113
|
### 3. WS Server: ./${name}.js --serve 8080 [--secret abc]
|
|
112
114
|
### 4. WS Client: ./${name}.js --connect ws://host:port/ws --secret abc
|
|
@@ -115,7 +117,7 @@ ${desc}
|
|
|
115
117
|
## OPTIONS:
|
|
116
118
|
--model --temp --tokens --top_p --context
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
`);
|
|
119
121
|
process.exit();
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -124,21 +126,21 @@ if (help) {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
const tool_call_name = '${name}';
|
|
127
|
-
const tool_call_description =
|
|
129
|
+
const tool_call_description = `${desc.trim()}
|
|
128
130
|
|
|
129
|
-
Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/${name}.md
|
|
131
|
+
Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/${name}.md`.trim();
|
|
130
132
|
|
|
131
133
|
const REPO_URL = 'https://codeberg.org/duin/hello-dave';
|
|
132
134
|
|
|
133
135
|
async function fetchLivePrompt() {
|
|
134
|
-
const promptUrl =
|
|
136
|
+
const promptUrl = `${REPO_URL}/raw/branch/main/docs/prompt/${name}.md`;
|
|
135
137
|
try {
|
|
136
138
|
const response = await fetch(promptUrl);
|
|
137
|
-
if (!response.ok) throw new Error(
|
|
139
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
138
140
|
return await response.text();
|
|
139
141
|
} catch (e) {
|
|
140
142
|
console.warn('Live prompt fetch failed, using fallback.');
|
|
141
|
-
return
|
|
143
|
+
return `${prompt_fallback || 'Default agent prompt.'}`;
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -154,11 +156,11 @@ agent.setup({
|
|
|
154
156
|
contextWindow
|
|
155
157
|
});
|
|
156
158
|
|
|
157
|
-
${generics || 'agent.addGenericToolcall("read_file"); agent.addGenericToolcall("write_file"); agent.addGenericToolcall("execute_bash_script");'}
|
|
159
|
+
${generics || 'agent.addGenericToolcall(\"read_file\"); agent.addGenericToolcall(\"write_file\"); agent.addGenericToolcall(\"execute_bash_script\"); agent.addGenericToolcall(\"javascript_interpreter\");'}
|
|
158
160
|
|
|
159
|
-
const cliIntro =
|
|
161
|
+
const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
|
|
160
162
|
${cli_intro || desc.substring(0,100) + '...'}
|
|
161
|
-
${launch_mode_note || ''}
|
|
163
|
+
${launch_mode_note || ''}`.trim();
|
|
162
164
|
|
|
163
165
|
if (input) {
|
|
164
166
|
const RES = await agent.directCall(input);
|
|
@@ -168,6 +170,6 @@ if (input) {
|
|
|
168
170
|
}
|
|
169
171
|
```
|
|
170
172
|
|
|
171
|
-
**In ALL responses**: Report MODE/LAUNCH_MODE after INSPECT. End with "Agent ${name} ready! Test: ./agents/${name}.js --help"
|
|
173
|
+
**In ALL responses**: Report MODE/LAUNCH_MODE after INSPECT. End with \"Agent ${name} ready! Test: ./agents/${name}.js --help\"
|
|
172
174
|
|
|
173
|
-
Date: April
|
|
175
|
+
Date: April 21, 2026. The write_file tool now auto-repairs escaping via syntax_check.sh --fix. Use clean template literals in generated code. 🚀 Enforce blueprint strictly. See docs/multi-agent-clusters.md.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Archived Infrastructure Tasks - 2026-04-21
|
|
2
|
+
|
|
3
|
+
These tasks related to VPS/CDN setup and publishing agents were removed from TODO.md as they are not core to the hello-dave project.
|
|
4
|
+
|
|
5
|
+
## Archived from TODO (high priority pending)
|
|
6
|
+
|
|
7
|
+
- Create a personal CDN on VPS using SSH + Nginx to serve static assets (images, HTML, JS) so that Grok / browse_page tools can directly access them without crawling or conversion issues
|
|
8
|
+
- Subtask: Set up VPS and SSH access
|
|
9
|
+
- Subtask: Install and configure Nginx for static file serving
|
|
10
|
+
- Subtask: Implement security measures (e.g., firewall, SSL/TLS, access controls)
|
|
11
|
+
- Subtask: Upload and organize static assets
|
|
12
|
+
- Subtask: Test with previous image test case and verify direct access via tools
|
|
13
|
+
|
|
14
|
+
- Create publish_agent.js in agents/ for secure deployment of documents to remote VPS (Nginx + ~/htdocs or /var/www/htdocs). Features: persistent config for multiple VPS (SSH host, port, user, htdocs path, http base URL), ask for missing endpoints on first use, generate long/obscure/randomized filenames for privacy (especially sensitive files), clean old/unused files in htdocs, rsync or scp with safety, support for static publishing of docs/markdown/html. Integrate with memory_agent for config persistence if possible. High privacy focus.
|
|
15
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Archived TODO Items - v0.1.0 (2026-04-24)
|
|
2
|
+
|
|
3
|
+
This archive contains all completed tasks from TODO.md as of 2026-04-24. No pending tasks remain.
|
|
4
|
+
|
|
5
|
+
## Previously High Priority Pending
|
|
6
|
+
- [x] 2026-04-24: Review and enhance genericToolset.js for better tool definitions, including validation for execute_bash_script parameters.
|
|
7
|
+
|
|
8
|
+
## Previously In Progress
|
|
9
|
+
- [x] 2026-04-23: Create dedicated test for execute_bash_script tool in scenarios/ folder. The test should be comprehensive, testing timeout, error cases, complex scripts, shebang, and safety (no escaping, CWD only). Reference the definition in lib/genericToolset.js
|
|
10
|
+
|
|
11
|
+
## Previously Later/Future Tasks
|
|
12
|
+
- [x] Review and add unit tests for agent toolsets (e.g., web_search, execute_bash_script in lib/)
|
|
13
|
+
- [x] Update CHANGELOG.md and prepare for v0.1.0 release (add new features like multi-agent improvements)
|
|
14
|
+
- [x] Enhance documentation: Add examples for hybrid spawn_agent modes in README.md and docs/
|
|
15
|
+
- [x] Implement additional agents: e.g., integration_agent for chaining Grok/OpenAI/Anthropic
|
|
16
|
+
- [x] Optimize WebSocket protocol: Implement suggested optimizations from docs/agent-dave-websocket-protocol.md
|
|
17
|
+
|
|
18
|
+
## Previously Done (Prior to This Archive)
|
|
19
|
+
- [x] 2026-04-24: Confirm genericToolset.js and its dedicated test are complete (including tests for tool definitions and validation).
|
|
20
|
+
- [x] 2026-04-22: Create TDD test in scenarios/ for escaping fix in syntax_check and write_file before any further implementation. Use test_agent if needed.
|
|
21
|
+
- [x] 2026-04-24: Implement bash tool fixes for execute_bash_script, including shebang support, strict mode enforcement, and input validation improvements.
|
|
22
|
+
- [x] 2026-04-24: Update syntax_check.sh with shebang, strict mode, and enhanced validation for safer script execution.
|
|
23
|
+
- [x] Document and review the "agent dave websocket protocol". Focus on communication between lib/AgentServer.js (central hub), lib/AgentClient.js (agent connections), and lib/wsIO.js (user interaction). Include description of the protocol, sequence diagrams if possible, and optimization suggestions.
|
|
24
|
+
- [x] Subtask: Review and analyze source files (AgentServer.js, AgentClient.js, wsIO.js)
|
|
25
|
+
- [x] Subtask: Summarize findings from review - Protocol uses JSON messages with 'action' from fixed ACTIONS list, bidirectional query/response with IDs for matching, introduction for registration, user_* for CLI/WS interaction, reset handling with epoch, pending response map with timeout. Suggestion: Add sequence diagram in Mermaid and document in docs/agent-dave-websocket-protocol.md.
|
|
26
|
+
- [x] Subtask: Document the protocol structure and message formats
|
|
27
|
+
- [x] Subtask: Create sequence diagrams for key interactions (Mermaid diagrams added to the new docs file)
|
|
28
|
+
- [x] Subtask: Identify and suggest optimizations for performance and reliability (detailed section added with 6 categories of improvements)
|
|
29
|
+
|
|
30
|
+
## Archive Notes
|
|
31
|
+
Older tasks archived on 2026-04-20 to docs/todo-archive-v0.0.9.md. Previous archives: docs/todo-archive-v0.0.8.md (2026-04-15), docs/todo-archive.md (2026-04-13).
|
|
32
|
+
Infrastructure tasks archived to docs/todo-archive-infra-2026-04-21.md on 2026-04-21.
|
package/lib/fafs.js
CHANGED
|
@@ -45,7 +45,7 @@ new CacheAsync(APP_CACHE, true, 'bin');
|
|
|
45
45
|
* @property {string} secret - Secret key for cache encryption/security
|
|
46
46
|
*/
|
|
47
47
|
const GLOBAL = {
|
|
48
|
-
max_recursive_requests:
|
|
48
|
+
max_recursive_requests: 50,
|
|
49
49
|
default_cache: APP_CACHE,
|
|
50
50
|
secret: 'tdb.e3a0cd73dd6a429283f921f9fc1bad41'
|
|
51
51
|
}
|
|
@@ -126,4 +126,4 @@ export {
|
|
|
126
126
|
GLOBAL,
|
|
127
127
|
env,
|
|
128
128
|
systemInfo
|
|
129
|
-
}
|
|
129
|
+
}
|
package/lib/genericToolset.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
9
|
|
|
10
|
-
// STANDARDIZED UTILS PATHS
|
|
10
|
+
// STANDARDIZED UTILS PATHS
|
|
11
11
|
const utilsDir = path.resolve(__dirname, '..', 'utils');
|
|
12
12
|
const searchSessionsSh = path.join(utilsDir, 'search_sessions.sh');
|
|
13
13
|
const listSessionsSh = path.join(utilsDir, 'list_sessions.sh');
|
|
@@ -26,47 +26,9 @@ ExternalIp: ${user.external_ip}
|
|
|
26
26
|
const tools = new ToolSet('auto');
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @private
|
|
33
|
-
*/
|
|
34
|
-
const getJSError = (errorStr) => {
|
|
35
|
-
let result = '';
|
|
36
|
-
const linematch = errorStr.match(/\[eval\]:(\d+)/);
|
|
37
|
-
const lineNumber = linematch ? linematch[1] : '';
|
|
38
|
-
const match = errorStr.split(/\" \"\[eval\]:\d+/s);
|
|
39
|
-
if (match.length > 1) {
|
|
40
|
-
const res = match[1].split('\n').slice(0, -10).join('\n');
|
|
41
|
-
result = `Error: line ${lineNumber}\n${res} `;
|
|
42
|
-
} else {
|
|
43
|
-
result = errorStr;
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
};
|
|
47
|
-
// /**
|
|
48
|
-
// * Try to prevent double escaping by LLMS models
|
|
49
|
-
// * When a string stays a string it is probably double escaped
|
|
50
|
-
// * @parameter {string} s
|
|
51
|
-
// * @returns {string}
|
|
52
|
-
// */
|
|
53
|
-
// const guessOverEscaping = (s) => {
|
|
54
|
-
// let test;
|
|
55
|
-
// if (typeof s === 'string') {
|
|
56
|
-
// try {
|
|
57
|
-
// test = JSON.parse(s);
|
|
58
|
-
// } catch (_e) { }
|
|
59
|
-
// if (typeof test === 'string') {
|
|
60
|
-
// return test;
|
|
61
|
-
// }
|
|
62
|
-
// }
|
|
63
|
-
// return s;
|
|
64
|
-
// }
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Try to prevent double escaping by LLMs.
|
|
68
|
-
* Handles common patterns like \"path\", \\\"path\\\", JSON strings,
|
|
69
|
-
* and multiple layers while being much safer on complex bash/JS scripts.
|
|
29
|
+
* Single source of truth for cleaning over-escaped strings from LLMs.
|
|
30
|
+
* Basic cleaning only. Heavy repair (especially for JS template literals)
|
|
31
|
+
* is now centralized in utils/syntax_check.sh (repair_file logic).
|
|
70
32
|
*/
|
|
71
33
|
const guessOverEscaping = (s) => {
|
|
72
34
|
if (typeof s !== 'string') return s;
|
|
@@ -74,13 +36,12 @@ const guessOverEscaping = (s) => {
|
|
|
74
36
|
let current = s.trim();
|
|
75
37
|
const seen = new Set();
|
|
76
38
|
let iterations = 0;
|
|
77
|
-
const MAX_ITER = 6;
|
|
39
|
+
const MAX_ITER = 6;
|
|
78
40
|
|
|
79
41
|
while (iterations < MAX_ITER && !seen.has(current)) {
|
|
80
42
|
seen.add(current);
|
|
81
43
|
iterations++;
|
|
82
44
|
|
|
83
|
-
// 1. Most reliable: JSON.parse (undoes proper JSON string escaping)
|
|
84
45
|
try {
|
|
85
46
|
const parsed = JSON.parse(current);
|
|
86
47
|
if (typeof parsed === 'string') {
|
|
@@ -89,24 +50,19 @@ const guessOverEscaping = (s) => {
|
|
|
89
50
|
}
|
|
90
51
|
} catch (e) { }
|
|
91
52
|
|
|
92
|
-
// 2. Specifically strip the pattern you saw: \"...\" (including the backslashes)
|
|
93
53
|
if (current.startsWith('\\"') && current.endsWith('\\"')) {
|
|
94
54
|
current = current.slice(2, -2);
|
|
95
55
|
continue;
|
|
96
56
|
}
|
|
97
57
|
|
|
98
|
-
// 3. Strip normal outer quotes, but protect JSON objects/arrays
|
|
99
58
|
if (current.startsWith('"') && current.endsWith('"') && current.length > 2) {
|
|
100
|
-
const inner = current.slice(1, -1);
|
|
101
|
-
|
|
102
|
-
if (!trimmedInner.startsWith('{') && !trimmedInner.startsWith('[')) {
|
|
59
|
+
const inner = current.slice(1, -1).trim();
|
|
60
|
+
if (!inner.startsWith('{') && !inner.startsWith('[')) {
|
|
103
61
|
current = inner;
|
|
104
62
|
continue;
|
|
105
63
|
}
|
|
106
64
|
}
|
|
107
65
|
|
|
108
|
-
// 4. Conservative global unescape of \" → "
|
|
109
|
-
// Only applied to short strings without newlines (i.e. not full scripts)
|
|
110
66
|
if (!current.includes('\n') && (current.match(/\\"/g) || []).length >= 1) {
|
|
111
67
|
const lessEscaped = current.replace(/\\"/g, '"');
|
|
112
68
|
if (lessEscaped !== current) {
|
|
@@ -115,20 +71,35 @@ const guessOverEscaping = (s) => {
|
|
|
115
71
|
}
|
|
116
72
|
}
|
|
117
73
|
|
|
118
|
-
break;
|
|
74
|
+
break;
|
|
119
75
|
}
|
|
120
76
|
|
|
121
77
|
return current;
|
|
122
|
-
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getJSError = (errorStr) => {
|
|
81
|
+
let result = '';
|
|
82
|
+
const linematch = errorStr.match(/\[eval\]:(\d+)/);
|
|
83
|
+
const lineNumber = linematch ? linematch[1] : '';
|
|
84
|
+
const match = errorStr.split(/" "\[eval\]:\d+/s);
|
|
85
|
+
if (match.length > 1) {
|
|
86
|
+
const res = match[1].split('\n').slice(0, -10).join('\n');
|
|
87
|
+
result = `Error: line ${lineNumber}\n${res} `;
|
|
88
|
+
} else {
|
|
89
|
+
result = errorStr;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
123
95
|
* @module lib/genericToolset
|
|
124
|
-
* Secure utility tools
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* @see {@link module:./index~ToolSet}
|
|
96
|
+
* Secure utility tools.
|
|
97
|
+
* write_file writes content, then runs syntax_check.sh WITHOUT --fix.
|
|
98
|
+
* On syntax error the error details are returned in the tool response
|
|
99
|
+
* (file is kept so LLM agents can read_file, inspect escaping issues,
|
|
100
|
+
* and submit a corrected version on the next write_file call).
|
|
131
101
|
*/
|
|
102
|
+
|
|
132
103
|
tools.add(
|
|
133
104
|
'javascript_interpreter',
|
|
134
105
|
'Execute ESM ES6 JavaScript on node.',
|
|
@@ -167,34 +138,57 @@ tools.add(
|
|
|
167
138
|
|
|
168
139
|
tools.add(
|
|
169
140
|
'execute_bash_script',
|
|
170
|
-
'Execute raw bash script or command (no escaping needed). Supports timeout
|
|
141
|
+
'Execute raw bash script or command (no escaping needed). Supports timeout.',
|
|
171
142
|
{
|
|
172
143
|
type: 'object',
|
|
173
144
|
properties: {
|
|
174
145
|
bash_script: {
|
|
175
146
|
type: 'string',
|
|
176
|
-
description: `
|
|
147
|
+
description: `
|
|
148
|
+
**Write raw bash exactly** as it should appear in a .sh file. Use normal bash escaping only (\`'\\''\` for single quotes inside single quotes, \`\\$\` for literal dollar, etc.).
|
|
149
|
+
|
|
150
|
+
Do NOT add extra backslashes for JSON, JavaScript, or XML. The system parses the JSON then passes your exact text to bash via stdin.
|
|
151
|
+
|
|
152
|
+
Correct example you should output in the parameter:
|
|
153
|
+
|
|
154
|
+
\`\`\`bash
|
|
155
|
+
echo 'Single quotes: '\\''quoted'\\'''
|
|
156
|
+
echo "Double quotes: \\"quoted\\""
|
|
157
|
+
echo "Dollar: \\$HOME = $HOME"
|
|
158
|
+
echo \\\`date\\\`
|
|
159
|
+
echo -e 'Pipe test:\\n1\\n2' | cat
|
|
160
|
+
\`\`\`
|
|
161
|
+
Supports all syntax safely via stdin. System: ${user.system}`
|
|
177
162
|
},
|
|
178
163
|
timeout: {
|
|
179
164
|
type: 'number',
|
|
180
165
|
default: 360,
|
|
181
|
-
description: 'Max execution time in seconds (default 360
|
|
166
|
+
description: 'Max execution time in seconds (default 360).'
|
|
167
|
+
},
|
|
168
|
+
strict: {
|
|
169
|
+
type:'boolean' ,
|
|
170
|
+
default: false,
|
|
171
|
+
description: 'Validate bash script STRICT first before it is being executed.'
|
|
182
172
|
}
|
|
183
173
|
},
|
|
184
174
|
required: ['bash_script']
|
|
185
175
|
},
|
|
186
176
|
async (params) => {
|
|
177
|
+
function escapeForBashTemplate(bashContent) {
|
|
178
|
+
return bashContent;
|
|
179
|
+
}
|
|
187
180
|
const timeoutSec = Number(params.timeout ?? 300);
|
|
188
|
-
const
|
|
181
|
+
const prams = params.strict? '' : '-S error';
|
|
182
|
+
const bash_script = escapeForBashTemplate(params.bash_script);
|
|
189
183
|
if (isNaN(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
184
|
+
console.log(bash_script);
|
|
190
185
|
const timeout = timeoutSec * 1000;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
`.options({ timeout }).run();
|
|
197
|
-
|
|
186
|
+
const valid = SH`shellcheck ${prams} -`.runSync(bash_script).stdout.toString().trim();
|
|
187
|
+
if (valid !== '') {
|
|
188
|
+
console.log(valid);
|
|
189
|
+
throw new Error(valid);
|
|
190
|
+
}
|
|
191
|
+
return await SH`bash`.options({ timeout }).run(bash_script);
|
|
198
192
|
}
|
|
199
193
|
);
|
|
200
194
|
|
|
@@ -243,7 +237,7 @@ tools.add(
|
|
|
243
237
|
|
|
244
238
|
tools.add(
|
|
245
239
|
'execute_remote_script',
|
|
246
|
-
'Run bash on remote via SSH.
|
|
240
|
+
'Run bash on remote via SSH.',
|
|
247
241
|
{
|
|
248
242
|
type: 'object',
|
|
249
243
|
properties: {
|
|
@@ -252,7 +246,7 @@ tools.add(
|
|
|
252
246
|
timeout: {
|
|
253
247
|
type: 'number',
|
|
254
248
|
default: 30,
|
|
255
|
-
description: 'Max execution time in seconds (default 30).
|
|
249
|
+
description: 'Max execution time in seconds (default 30).'
|
|
256
250
|
}
|
|
257
251
|
},
|
|
258
252
|
required: ['url', 'script']
|
|
@@ -307,8 +301,8 @@ tools.add(
|
|
|
307
301
|
},
|
|
308
302
|
async (params) => {
|
|
309
303
|
let file = guessOverEscaping(params.file?.trim());
|
|
310
|
-
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('
|
|
311
|
-
throw new Error('Relative CWD path only (no /, ..,
|
|
304
|
+
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\')) {
|
|
305
|
+
throw new Error('Relative CWD path only (no /, .., \\).');
|
|
312
306
|
}
|
|
313
307
|
const resolved = path.resolve(process.cwd(), file);
|
|
314
308
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
@@ -318,7 +312,7 @@ tools.add(
|
|
|
318
312
|
|
|
319
313
|
tools.add(
|
|
320
314
|
'write_file',
|
|
321
|
-
'Write/validate file in CWD.
|
|
315
|
+
'Write/validate file in CWD. Writes content to file then runs syntax_check.sh (no --fix). Syntax errors (if any) are reported back in the response so LLM can inspect escaping and correct on next call. File is kept on error.',
|
|
322
316
|
{
|
|
323
317
|
type: 'object',
|
|
324
318
|
properties: {
|
|
@@ -328,52 +322,39 @@ tools.add(
|
|
|
328
322
|
required: ['file', 'content']
|
|
329
323
|
},
|
|
330
324
|
async (params) => {
|
|
331
|
-
let
|
|
332
|
-
let content = guessOverEscaping(params.content
|
|
333
|
-
|
|
334
|
-
|
|
325
|
+
let fileParam = guessOverEscaping(params.file?.trim());
|
|
326
|
+
let content = guessOverEscaping(params.content ? params.content : '');
|
|
327
|
+
|
|
328
|
+
if (typeof fileParam !== 'string' || !fileParam || fileParam.startsWith('/') || fileParam.includes('..') || fileParam.includes('\\')) {
|
|
329
|
+
throw new Error('Relative CWD path only (no /, .., \\).');
|
|
335
330
|
}
|
|
336
|
-
const resolved = path.resolve(process.cwd(),
|
|
331
|
+
const resolved = path.resolve(process.cwd(), fileParam);
|
|
337
332
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
338
|
-
const dir = path.dirname(resolved);
|
|
339
|
-
const ext = path.extname(file);
|
|
340
|
-
let finalContent = content, validationMsg = '';
|
|
341
333
|
|
|
342
|
-
const
|
|
343
|
-
await fs.
|
|
334
|
+
const dir = path.dirname(resolved);
|
|
335
|
+
await fs.mkdir(dir, { recursive: true });
|
|
336
|
+
await fs.writeFile(resolved, content, 'utf8');
|
|
344
337
|
|
|
338
|
+
let validationMsg = '';
|
|
345
339
|
try {
|
|
346
|
-
await SH`${syntaxCheckSh} ${
|
|
347
|
-
validationMsg = ' ✓ Syntax OK';
|
|
348
|
-
} catch (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
let fixed = content.replace(/\\\\`/g, '\\`').replace(/\\\`/g, '`').replace(/\\`/g, '`').replace(/\\\$/g, '$').replace(/\\\${/g, '${');
|
|
354
|
-
const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2, 6)}.js`);
|
|
355
|
-
await fs.writeFile(temp2, fixed, 'utf8');
|
|
356
|
-
try {
|
|
357
|
-
await SH`${syntaxCheckSh} ${[temp2]}`.run();
|
|
358
|
-
finalContent = fixed;
|
|
359
|
-
await fs.rename(temp2, resolved);
|
|
360
|
-
await fs.unlink(temp1).catch(() => { });
|
|
361
|
-
validationMsg = ' ✓ Fixed JS';
|
|
362
|
-
} catch {
|
|
363
|
-
await fs.unlink(temp1).catch(() => { });
|
|
364
|
-
await fs.unlink(temp2).catch(() => { });
|
|
365
|
-
throw new Error(`Syntax error (fix failed): ${e.message}`);
|
|
366
|
-
}
|
|
340
|
+
const checkOutput = await SH`${syntaxCheckSh} ${resolved}`.run();
|
|
341
|
+
validationMsg = checkOutput ? ` ${checkOutput.trim()}` : ' ✓ Syntax OK';
|
|
342
|
+
} catch (checkErr) {
|
|
343
|
+
const errText = checkErr?.toString() || 'Unknown syntax error';
|
|
344
|
+
validationMsg = ` [Syntax Error] ${getJSError(errText) || errText}`;
|
|
345
|
+
// File is intentionally kept so LLM can read_file and fix escaping
|
|
367
346
|
}
|
|
368
347
|
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
348
|
+
if (content.trim().startsWith('#!')) {
|
|
349
|
+
try {
|
|
350
|
+
await SH`chmod +x ${[resolved]}`.run();
|
|
351
|
+
validationMsg += ' ✓ +x';
|
|
352
|
+
} catch (e) {
|
|
353
|
+
validationMsg += ' (chmod failed)';
|
|
354
|
+
}
|
|
374
355
|
}
|
|
375
356
|
|
|
376
|
-
return `Wrote ${
|
|
357
|
+
return `Wrote ${fileParam} (${Buffer.byteLength(content, 'utf8')} bytes).${validationMsg}`;
|
|
377
358
|
}
|
|
378
359
|
);
|
|
379
360
|
|
|
@@ -392,7 +373,14 @@ tools.add(
|
|
|
392
373
|
if (typeof file !== 'string' || !file) throw new Error('Relative path required.');
|
|
393
374
|
const resolved = path.resolve(process.cwd(), file);
|
|
394
375
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
395
|
-
|
|
376
|
+
|
|
377
|
+
const args = resolved;
|
|
378
|
+
try {
|
|
379
|
+
const out = await SH`${syntaxCheckSh} ${args}`.run();
|
|
380
|
+
return out || 'Syntax validation passed';
|
|
381
|
+
} catch (e) {
|
|
382
|
+
return `Syntax check failed: ${e.toString()}`;
|
|
383
|
+
}
|
|
396
384
|
}
|
|
397
385
|
);
|
|
398
386
|
|
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.
|
|
4
|
+
"version": "0.0.10",
|
|
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",
|
package/utils/syntax_check.sh
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# utils/syntax_check.sh - Multi-language syntax validation
|
|
3
|
-
# Usage: ./utils/syntax_check.sh <file>
|
|
4
|
-
# Detects lang from ext/shebang, runs checker.
|
|
5
|
-
#
|
|
3
|
+
# Usage: ./utils/syntax_check.sh <file>
|
|
4
|
+
# Detects lang from ext/shebang, runs checker.
|
|
5
|
+
# Captures stdout+stderr from each syntax tool and echoes it back to STDOUT.
|
|
6
|
+
# Returns 0=OK, 1=error. Integrates with write_file lib/genericToolset.js tool.
|
|
6
7
|
|
|
7
8
|
set -euo pipefail
|
|
8
9
|
|
|
9
10
|
FILE="${1?Error: Provide file path}"
|
|
10
|
-
|
|
11
|
-
FIX="${VERBOSE:0:4}==--fix"
|
|
11
|
+
MODE="${2:-}"
|
|
12
12
|
|
|
13
13
|
[ ! -f "$FILE" ] && { echo "❌ File not found: $FILE"; exit 1; }
|
|
14
14
|
|
|
15
15
|
# Detect language
|
|
16
16
|
EXT="${FILE##*.}"
|
|
17
|
-
SHEBANG=$(head -n1 "$FILE" 2>/dev/null | cut -d' ' -f1)
|
|
17
|
+
SHEBANG=$(head -n1 "$FILE" 2>/dev/null | cut -d' ' -f1 || true)
|
|
18
18
|
|
|
19
19
|
detect_lang() {
|
|
20
20
|
case "$SHEBANG" in
|
|
@@ -35,26 +35,70 @@ detect_lang() {
|
|
|
35
35
|
LANG=$(detect_lang)
|
|
36
36
|
echo "🔍 Validating $FILE (lang: $LANG)"
|
|
37
37
|
|
|
38
|
+
# Helper to run checker, capture all output (stdout+stderr), echo it, then print status
|
|
39
|
+
run_check() {
|
|
40
|
+
local cmd="$1"
|
|
41
|
+
local label="$2"
|
|
42
|
+
local ok_msg="✅ ${label} OK"
|
|
43
|
+
local err_msg="❌ ${label} syntax error"
|
|
44
|
+
|
|
45
|
+
echo "→ Running: $cmd"
|
|
46
|
+
if output=$(eval "$cmd" 2>&1); then
|
|
47
|
+
if [ -n "$output" ]; then
|
|
48
|
+
echo "$output"
|
|
49
|
+
fi
|
|
50
|
+
echo "$ok_msg"
|
|
51
|
+
return 0
|
|
52
|
+
else
|
|
53
|
+
if [ -n "$output" ]; then
|
|
54
|
+
echo "$output"
|
|
55
|
+
else
|
|
56
|
+
echo "(No output from checker - exited with error)"
|
|
57
|
+
fi
|
|
58
|
+
echo "$err_msg"
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
case "$LANG" in
|
|
39
64
|
js)
|
|
40
|
-
node --check "$FILE"
|
|
65
|
+
run_check "node --check \"$FILE\"" "JS" || exit 1
|
|
41
66
|
;;
|
|
42
67
|
py)
|
|
43
|
-
python3 -m py_compile "$FILE"
|
|
68
|
+
run_check "python3 -m py_compile \"$FILE\"" "Python" || exit 1
|
|
44
69
|
;;
|
|
45
70
|
bash|sh)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
71
|
+
run_check "shellcheck \"$FILE\"" "Bash" || exit 1
|
|
72
|
+
|
|
73
|
+
# Optional: shellcheck (warnings only, does not fail build)
|
|
74
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
75
|
+
echo "→ Running: shellcheck \"$FILE\""
|
|
76
|
+
if shell_output=$(shellcheck "$FILE" 2>&1); then
|
|
77
|
+
if [ -n "$shell_output" ]; then
|
|
78
|
+
echo "$shell_output"
|
|
79
|
+
fi
|
|
80
|
+
echo "✅ Shellcheck passed"
|
|
81
|
+
else
|
|
82
|
+
echo "$shell_output"
|
|
83
|
+
echo "⚠️ Shellcheck found issues (warnings only)"
|
|
84
|
+
fi
|
|
50
85
|
fi
|
|
51
86
|
;;
|
|
52
87
|
json)
|
|
53
|
-
node -e
|
|
88
|
+
run_check "node -e '
|
|
89
|
+
try {
|
|
90
|
+
JSON.parse(require(\"fs\").readFileSync(process.argv[1], \"utf8\"));
|
|
91
|
+
console.log(\"JSON is valid\");
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(e.message);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
' \"$FILE\"" "JSON" || exit 1
|
|
54
97
|
;;
|
|
55
98
|
unknown)
|
|
56
|
-
echo "⚠️ Unknown
|
|
57
|
-
|
|
99
|
+
echo "⚠️ Unknown language for extension .$EXT / shebang: $SHEBANG"
|
|
100
|
+
echo "✅ Skipping syntax check"
|
|
101
|
+
exit 0
|
|
58
102
|
;;
|
|
59
103
|
esac
|
|
60
104
|
|