@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 CHANGED
@@ -1,11 +1,5 @@
1
1
  ## TODO (high priority pending)
2
- - [ ] 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
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
- - [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.
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
- 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).
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).
@@ -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'; // Added for local fallback
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
- Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/spawn_agent.md`.trim();
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
- // Priority 1: Remote
101
- const promptUrl = `${REPO_URL}/raw/branch/main/docs/prompt/spawn_agent.md`;
102
- const response = await fetch(promptUrl);
103
- if (!response.ok) {
104
- throw new Error(`HTTP ${response.status}`);
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
- return await response.text();
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
+ }
@@ -0,0 +1,7 @@
1
+
2
+
3
+ ## A linux based os with
4
+
5
+ `node`
6
+ `spellcheck`
7
+ `msmtp`
@@ -1,59 +1,61 @@
1
- You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents/<name>.js). **Tools use exec CWD** (/tmp OK).
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&gt;/dev/null || echo 'MISSING: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact'
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&gt;/dev/null | wc -l 2&gt;/dev/null || echo 0)
16
- PM2_CHECK=$(pm2 list 2&gt;/dev/null | grep hello-dave || echo Standalone)
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 &gt;0): Full auto-deploy/validate/test using tools.
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: ..."). Reject if invalid.
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/&lt;name&gt;.md or hardcoded), cliIntro.
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 "&lt;generated_code&gt;"
43
- - execute_bash_script "node --check temp_agent.js &amp;&amp; echo 'SYNTAX:OK' || echo 'SYNTAX:FAIL'"
44
- - execute_bash_script "grep -q 'agent.start(' temp_agent.js &amp;&amp; grep -q 'new AgentManager' temp_agent.js &amp;&amp; echo 'BLUEPRINT:OK' || echo 'BLUEPRINT:FAIL'"
45
- - If both OK: execute_bash_script "mv temp_agent.js agents/${name}.js &amp;&amp; chmod +x agents/${name}.js &amp;&amp; echo 'DEPLOYED'"
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; echo '&lt;code&gt;' &gt; 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 &amp;&amp; typeof args._[0] === 'string' &amp;&amp; args._[0].trim() !== '') {
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(\`'\${name} --help' You are looking at it.
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 = \`${desc.trim()}
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\`.trim();
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 = \`\${REPO_URL}/raw/branch/main/docs/prompt/${name}.md\`;
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(\`HTTP \${response.status}\`);
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 \`${prompt_fallback || 'Default agent prompt.'}\`;
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 = \`🤖 \${name} (\${options.model}) ready! (context: \${contextWindow})
161
+ const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
160
162
  ${cli_intro || desc.substring(0,100) + '...'}
161
- ${launch_mode_note || ''}\`.trim();
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 13, 2026. 🚀 Enforce blueprint strictly. See docs/multi-agent-clusters.md.
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: 20,
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
+ }
@@ -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 (hoisted constants for performance/consistency)
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
- * 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
- */
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; // safety limit
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
- const trimmedInner = inner.trim();
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; // no more productive changes
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 for code execution, file I/O, system ops. Pre-populated ToolSet.
125
- *
126
- * @example
127
- * import tools from './lib/genericToolset.js';
128
- * await tools.call('get_user_env', {});
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 to prevent hangs.',
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: `Raw bash verbatim. Supports all syntax safely via stdin. System: ${user.system}`
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 seconds, 0 is no timeout). Uses @j-o-r/sh timeout (ms) with SIGTERM on expiry to prevent hangs from interactive prompts or slow commands.'
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 bash_script = guessOverEscaping(params.bash_script);
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
- // return await SH`bash`.options({ timeout: timeoutMs }).run(params.bash_script);
192
- const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
193
- return await SH`bash <<'${delim}'
194
- ${bash_script}
195
- ${delim}
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. Supports timeout to prevent hangs.',
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). Uses @j-o-r/sh timeout (ms) with SIGTERM on SSH process expiry.'
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. Auto-syntax check + JS fix + chmod.',
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 file = guessOverEscaping(params.file?.trim());
332
- let content = guessOverEscaping(params.content ?? '');
333
- if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
334
- throw new Error('Relative CWD path only.');
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(), file);
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 temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2, 6)}${ext || '.tmp'}`);
343
- await fs.writeFile(temp1, content, 'utf8');
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} ${[temp1]}`.run();
347
- validationMsg = ' ✓ Syntax OK';
348
- } catch (e) {
349
- if (!['.js', '.mjs'].includes(ext)) {
350
- await fs.unlink(temp1).catch(() => { });
351
- throw new Error(`Syntax error: ${e.message}`);
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 (validationMsg === ' ✓ Syntax OK') await fs.rename(temp1, resolved);
370
-
371
- if (finalContent.startsWith('#!')) {
372
- await SH`chmod +x ${[resolved]}`.run();
373
- validationMsg += ' ✓ +x';
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 ${file} (${Buffer.byteLength(finalContent, 'utf8')} bytes).${validationMsg}`;
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
- return await SH`${syntaxCheckSh} ${[resolved]}`.run();
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.9",
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",
@@ -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> [--fix|--verbose]
4
- # Detects lang from ext/shebang, runs checker. Returns 0=OK, 1=error.
5
- # Integrates with write_file tool retries.
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
- VERBOSE="${2:-}"
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" && echo "JS OK" || { echo "❌ JS syntax error"; exit 1; }
65
+ run_check "node --check \"$FILE\"" "JS" || exit 1
41
66
  ;;
42
67
  py)
43
- python3 -m py_compile "$FILE" && echo "Python OK" || { echo "❌ Python syntax error"; exit 1; }
68
+ run_check "python3 -m py_compile \"$FILE\"" "Python" || exit 1
44
69
  ;;
45
70
  bash|sh)
46
- bash -n "$FILE" && echo "Bash OK" || { echo "❌ Bash syntax error"; exit 1; }
47
- # Optional: shellcheck if installed
48
- if command -v shellcheck >/dev/null; then
49
- shellcheck "$FILE" || echo "⚠️ Shellcheck warnings"
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 "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$FILE" && echo "✅ JSON OK" || { echo "❌ JSON invalid"; exit 1; }
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 lang for $EXT / shebang: $SHEBANG"
57
- exit 0 # Non-fatal
99
+ echo "⚠️ Unknown language for extension .$EXT / shebang: $SHEBANG"
100
+ echo "✅ Skipping syntax check"
101
+ exit 0
58
102
  ;;
59
103
  esac
60
104