nothumanallowed 8.5.1 → 8.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.mjs +4 -1
- package/src/commands/ask.mjs +148 -18
- package/src/commands/chat.mjs +115 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents + unified productivity suite. Gmail, Calendar, Drive, Contacts, Tasks, GitHub, Notion, Slack, voice chat, smart scheduler. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { spawnCore } from './spawn.mjs';
|
|
|
8
8
|
import { loadConfig, setConfigValue } from './config.mjs';
|
|
9
9
|
import { checkForUpdates, runUpdate, checkNpmVersion } from './updater.mjs';
|
|
10
10
|
import { download } from './downloader.mjs';
|
|
11
|
-
import { cmdAsk } from './commands/ask.mjs';
|
|
11
|
+
import { cmdAsk, cmdAgentCreate } from './commands/ask.mjs';
|
|
12
12
|
import { cmdPlan } from './commands/plan.mjs';
|
|
13
13
|
import { cmdTasks } from './commands/tasks.mjs';
|
|
14
14
|
import { cmdOps } from './commands/ops.mjs';
|
|
@@ -57,6 +57,9 @@ export async function main(argv) {
|
|
|
57
57
|
case 'ask':
|
|
58
58
|
return cmdAsk(args);
|
|
59
59
|
|
|
60
|
+
case 'agent:create':
|
|
61
|
+
return cmdAgentCreate(args);
|
|
62
|
+
|
|
60
63
|
case 'run':
|
|
61
64
|
return cmdRun(args);
|
|
62
65
|
|
package/src/commands/ask.mjs
CHANGED
|
@@ -18,9 +18,12 @@ export async function cmdAsk(args) {
|
|
|
18
18
|
if (!agentName || agentName.startsWith('-')) {
|
|
19
19
|
fail('Usage: nha ask <agent> "your question"');
|
|
20
20
|
fail(' nha ask saber "Audit this Express app for OWASP Top 10"');
|
|
21
|
-
fail(' nha ask oracle "Analyze this CSV
|
|
21
|
+
fail(' nha ask oracle "Analyze this CSV" --file data.csv');
|
|
22
|
+
fail(' nha ask forge "What\'s in this?" --image screenshot.png');
|
|
22
23
|
console.log('');
|
|
23
24
|
info('Available agents: ' + AGENTS.join(', '));
|
|
25
|
+
info('Custom agents in ~/.nha/agents/ are also available.');
|
|
26
|
+
info('Create one: nha agent:create myagent "Expert in X" "You are..."');
|
|
24
27
|
process.exit(1);
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -28,6 +31,7 @@ export async function cmdAsk(args) {
|
|
|
28
31
|
if (!fs.existsSync(agentFile)) {
|
|
29
32
|
fail(`Agent "${agentName}" not found in ~/.nha/agents/`);
|
|
30
33
|
info('Available: ' + AGENTS.join(', '));
|
|
34
|
+
info('Create custom: nha agent:create <name> <tagline> <system-prompt>');
|
|
31
35
|
process.exit(1);
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -36,32 +40,33 @@ export async function cmdAsk(args) {
|
|
|
36
40
|
let model = null;
|
|
37
41
|
let stream = true;
|
|
38
42
|
let attachFile = null;
|
|
43
|
+
let attachImage = null;
|
|
39
44
|
|
|
40
45
|
for (let i = 1; i < args.length; i++) {
|
|
41
46
|
if (args[i] === '--provider' && args[i + 1]) { provider = args[++i]; continue; }
|
|
42
47
|
if (args[i] === '--model' && args[i + 1]) { model = args[++i]; continue; }
|
|
43
48
|
if (args[i] === '--no-stream') { stream = false; continue; }
|
|
44
49
|
if (args[i] === '--file' && args[i + 1]) { attachFile = args[++i]; continue; }
|
|
50
|
+
if (args[i] === '--image' && args[i + 1]) { attachImage = args[++i]; continue; }
|
|
45
51
|
promptParts.push(args[i]);
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
let userMessage = promptParts.join(' ');
|
|
49
|
-
if (!userMessage) {
|
|
50
|
-
fail('No prompt provided.');
|
|
51
|
-
fail('Usage: nha ask saber "your question here"');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
55
|
|
|
55
56
|
if (attachFile) {
|
|
56
57
|
const filePath = path.resolve(attachFile);
|
|
57
|
-
if (!fs.existsSync(filePath)) {
|
|
58
|
-
fail(`File not found: ${attachFile}`);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
58
|
+
if (!fs.existsSync(filePath)) { fail(`File not found: ${attachFile}`); process.exit(1); }
|
|
61
59
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
62
60
|
const maxChars = 100_000;
|
|
63
61
|
const truncated = content.length > maxChars ? content.slice(0, maxChars) + '\n\n[... truncated ...]' : content;
|
|
64
|
-
|
|
62
|
+
info(`Attached: ${path.basename(filePath)} (${Math.round(content.length / 1024)} KB)`);
|
|
63
|
+
userMessage = (userMessage || 'Analyze this file') + `\n\n--- Attached file: ${path.basename(filePath)} ---\n${truncated}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!userMessage && !attachImage) {
|
|
67
|
+
fail('No prompt provided.');
|
|
68
|
+
fail('Usage: nha ask saber "your question here"');
|
|
69
|
+
process.exit(1);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
const config = loadConfig();
|
|
@@ -85,16 +90,92 @@ export async function cmdAsk(args) {
|
|
|
85
90
|
console.log(`\n ${BOLD}${card?.displayName || agentName.toUpperCase()}${NC} ${D}(${card?.tagline || card?.category || 'agent'})${NC}`);
|
|
86
91
|
console.log(` ${D}Provider: ${provider}${model ? ' / ' + model : ''} | Direct call — no server${NC}\n`);
|
|
87
92
|
|
|
88
|
-
const callFn = getProviderCall(provider);
|
|
89
|
-
if (!callFn) {
|
|
90
|
-
fail(`Unknown provider: ${provider}`);
|
|
91
|
-
info('Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
93
|
const startTime = Date.now();
|
|
96
94
|
|
|
97
95
|
try {
|
|
96
|
+
// Image attachment — use vision API
|
|
97
|
+
if (attachImage) {
|
|
98
|
+
const imagePath = path.resolve(attachImage);
|
|
99
|
+
if (!fs.existsSync(imagePath)) { fail(`Image not found: ${attachImage}`); process.exit(1); }
|
|
100
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
101
|
+
const base64 = imageBuffer.toString('base64');
|
|
102
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
103
|
+
const mimeMap = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.gif': 'image/gif' };
|
|
104
|
+
const mimeType = mimeMap[ext] || 'image/jpeg';
|
|
105
|
+
info(`Attached image: ${path.basename(imagePath)} (${Math.round(base64.length * 3 / 4 / 1024)} KB)`);
|
|
106
|
+
|
|
107
|
+
const imagePrompt = userMessage || 'Describe this image in detail. Extract any text, data, or important information.';
|
|
108
|
+
let response = '';
|
|
109
|
+
|
|
110
|
+
if (provider === 'anthropic') {
|
|
111
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
model: model || 'claude-sonnet-4-20250514', max_tokens: 8192, system: systemPrompt,
|
|
116
|
+
messages: [{ role: 'user', content: [
|
|
117
|
+
{ type: 'image', source: { type: 'base64', media_type: mimeType, data: base64 } },
|
|
118
|
+
{ type: 'text', text: imagePrompt },
|
|
119
|
+
]}],
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) throw new Error(`Anthropic ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
123
|
+
const data = await res.json();
|
|
124
|
+
response = data.content?.[0]?.text || '';
|
|
125
|
+
} else if (provider === 'openai') {
|
|
126
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
model: model || 'gpt-4o-mini', max_tokens: 8192,
|
|
131
|
+
messages: [
|
|
132
|
+
{ role: 'system', content: systemPrompt },
|
|
133
|
+
{ role: 'user', content: [
|
|
134
|
+
{ type: 'image_url', image_url: { url: `data:${mimeType};base64,${base64}` } },
|
|
135
|
+
{ type: 'text', text: imagePrompt },
|
|
136
|
+
]},
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) throw new Error(`OpenAI ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
141
|
+
const data = await res.json();
|
|
142
|
+
response = data.choices?.[0]?.message?.content || '';
|
|
143
|
+
} else if (provider === 'gemini') {
|
|
144
|
+
const m = model || 'gemini-2.0-flash';
|
|
145
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${m}:generateContent?key=${apiKey}`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
system_instruction: { parts: [{ text: systemPrompt }] },
|
|
150
|
+
contents: [{ parts: [
|
|
151
|
+
{ inline_data: { mime_type: mimeType, data: base64 } },
|
|
152
|
+
{ text: imagePrompt },
|
|
153
|
+
]}],
|
|
154
|
+
generationConfig: { maxOutputTokens: 8192 },
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) throw new Error(`Gemini ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
158
|
+
const data = await res.json();
|
|
159
|
+
response = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
160
|
+
} else {
|
|
161
|
+
fail(`Vision not supported for "${provider}". Use anthropic, openai, or gemini.`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(response);
|
|
166
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
167
|
+
console.log(`\n ${D}${elapsed}s | ${provider}${model ? ' / ' + model : ''} | ${card?.displayName || agentName}${NC}\n`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Text / file — standard LLM call
|
|
172
|
+
const callFn = getProviderCall(provider);
|
|
173
|
+
if (!callFn) {
|
|
174
|
+
fail(`Unknown provider: ${provider}`);
|
|
175
|
+
info('Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
98
179
|
const useStream = stream && (provider === 'anthropic' || provider === 'openai' || provider === 'deepseek' || provider === 'grok' || provider === 'mistral');
|
|
99
180
|
const result = await callFn(apiKey, model, systemPrompt, userMessage, useStream);
|
|
100
181
|
|
|
@@ -109,3 +190,52 @@ export async function cmdAsk(args) {
|
|
|
109
190
|
process.exit(1);
|
|
110
191
|
}
|
|
111
192
|
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* nha agent:create <name> <tagline> <system-prompt>
|
|
196
|
+
* Creates a custom agent file in ~/.nha/agents/
|
|
197
|
+
*/
|
|
198
|
+
export async function cmdAgentCreate(args) {
|
|
199
|
+
const name = (args[0] || '').toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
200
|
+
const tagline = args[1] || '';
|
|
201
|
+
const sysPrompt = args.slice(2).join(' ') || '';
|
|
202
|
+
|
|
203
|
+
if (!name || !tagline || !sysPrompt) {
|
|
204
|
+
fail('Usage: nha agent:create <name> "<tagline>" "<system prompt>"');
|
|
205
|
+
console.log('');
|
|
206
|
+
info('Example:');
|
|
207
|
+
info(' nha agent:create reviewer "Code review expert" "You are a senior code reviewer..."');
|
|
208
|
+
console.log('');
|
|
209
|
+
info('The agent will be available as: nha ask reviewer "review this code"');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const agentFile = path.join(AGENTS_DIR, `${name}.mjs`);
|
|
214
|
+
if (fs.existsSync(agentFile)) {
|
|
215
|
+
fail(`Agent "${name}" already exists at ${agentFile}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const content = `// NHA Custom Agent: ${name}
|
|
220
|
+
// Created: ${new Date().toISOString()}
|
|
221
|
+
|
|
222
|
+
export const CARD = {
|
|
223
|
+
name: '${name}',
|
|
224
|
+
displayName: '${name.toUpperCase()}',
|
|
225
|
+
category: 'custom',
|
|
226
|
+
tagline: '${tagline.replace(/'/g, "\\'")}',
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const SYSTEM_PROMPT = \`${sysPrompt.replace(/`/g, '\\`')}\`;
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
if (!fs.existsSync(AGENTS_DIR)) {
|
|
233
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(agentFile, content, 'utf-8');
|
|
237
|
+
ok(`Agent "${name}" created at ${agentFile}`);
|
|
238
|
+
info(`Invoke it: nha ask ${name} "your question"`);
|
|
239
|
+
info(`With file: nha ask ${name} "analyze" --file report.csv`);
|
|
240
|
+
info(`With image: nha ask ${name} "what is this?" --image photo.jpg`);
|
|
241
|
+
}
|
package/src/commands/chat.mjs
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import readline from 'readline';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
14
16
|
import { loadConfig } from '../config.mjs';
|
|
15
|
-
import {
|
|
17
|
+
import { AGENTS_DIR, AGENTS } from '../constants.mjs';
|
|
18
|
+
import { callLLM, parseAgentFile } from '../services/llm.mjs';
|
|
19
|
+
|
|
16
20
|
import { loadChatHistory, saveChatHistory, extractMemory } from '../services/memory.mjs';
|
|
17
21
|
import { fail, info, ok, warn, C, G, Y, D, W, BOLD, NC, R } from '../ui.mjs';
|
|
18
22
|
import {
|
|
@@ -160,17 +164,97 @@ async function handleSlashCommand(input, config, history) {
|
|
|
160
164
|
return true;
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
// /agent <name> — switch to talking with a specific agent
|
|
168
|
+
if (trimmed.startsWith('/agent ')) {
|
|
169
|
+
const agentName = trimmed.slice(7).trim().toLowerCase();
|
|
170
|
+
const agentFile = path.join(AGENTS_DIR, `${agentName}.mjs`);
|
|
171
|
+
if (!fs.existsSync(agentFile)) {
|
|
172
|
+
console.log(` ${R}Agent "${agentName}" not found. Available: ${AGENTS.join(', ')}${NC}`);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
const agentSource = fs.readFileSync(agentFile, 'utf-8');
|
|
176
|
+
const { card, systemPrompt: agentSysPrompt } = parseAgentFile(agentSource, agentName);
|
|
177
|
+
if (agentSysPrompt) {
|
|
178
|
+
// Store agent context for subsequent messages
|
|
179
|
+
config._chatAgent = { name: agentName, systemPrompt: agentSysPrompt, card };
|
|
180
|
+
console.log(` ${G}Now chatting with ${BOLD}${card?.displayName || agentName.toUpperCase()}${NC}${G} (${card?.tagline || 'agent'})${NC}`);
|
|
181
|
+
console.log(` ${D}Type /agent off to return to NHA Chat${NC}`);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(` ${R}Agent "${agentName}" has no system prompt.${NC}`);
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (trimmed === '/agent off' || trimmed === '/agent reset') {
|
|
189
|
+
delete config._chatAgent;
|
|
190
|
+
console.log(` ${G}Switched back to NHA Chat.${NC}`);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// /create-agent — interactive agent creation
|
|
195
|
+
if (trimmed === '/create-agent') {
|
|
196
|
+
const readline2 = await import('readline');
|
|
197
|
+
const rl2 = readline2.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
198
|
+
const q = (prompt) => new Promise(resolve => rl2.question(prompt, resolve));
|
|
199
|
+
console.log(`\n ${BOLD}${Y}Create Custom Agent${NC}\n`);
|
|
200
|
+
const name = ((await q(` ${C}Name${NC} (lowercase, no spaces): `)) || '').toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
201
|
+
const tagline = (await q(` ${C}Tagline${NC} (short description): `)) || '';
|
|
202
|
+
const sysPrompt = (await q(` ${C}System prompt${NC} (agent personality): `)) || '';
|
|
203
|
+
rl2.close();
|
|
204
|
+
|
|
205
|
+
if (!name || !tagline || !sysPrompt) {
|
|
206
|
+
console.log(` ${R}All fields required.${NC}`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const agentFile = path.join(AGENTS_DIR, `${name}.mjs`);
|
|
211
|
+
if (fs.existsSync(agentFile)) {
|
|
212
|
+
console.log(` ${R}Agent "${name}" already exists.${NC}`);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const content = `// NHA Custom Agent: ${name}\n// Created: ${new Date().toISOString()}\n\nexport const CARD = {\n name: '${name}',\n displayName: '${name.toUpperCase()}',\n category: 'custom',\n tagline: '${tagline.replace(/'/g, "\\'")}',\n};\n\nexport const SYSTEM_PROMPT = \`${sysPrompt.replace(/`/g, '\\`')}\`;\n`;
|
|
217
|
+
if (!fs.existsSync(AGENTS_DIR)) fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
218
|
+
fs.writeFileSync(agentFile, content, 'utf-8');
|
|
219
|
+
console.log(` ${G}Agent "${name}" created!${NC}`);
|
|
220
|
+
console.log(` ${D}Switch to it: /agent ${name}${NC}`);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// /agents — list available agents
|
|
225
|
+
if (trimmed === '/agents') {
|
|
226
|
+
const available = [];
|
|
227
|
+
if (fs.existsSync(AGENTS_DIR)) {
|
|
228
|
+
for (const f of fs.readdirSync(AGENTS_DIR)) {
|
|
229
|
+
if (f.endsWith('.mjs')) available.push(f.replace('.mjs', ''));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
console.log(` ${BOLD}Available Agents${NC} (${available.length})`);
|
|
233
|
+
for (const a of available) {
|
|
234
|
+
console.log(` ${C}${a}${NC}`);
|
|
235
|
+
}
|
|
236
|
+
console.log(`\n ${D}Switch: /agent <name> | Create: /create-agent${NC}`);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
163
240
|
if (trimmed === '/help') {
|
|
164
241
|
console.log(`
|
|
165
242
|
${BOLD}Chat Commands${NC}
|
|
166
243
|
|
|
167
|
-
${C}/tasks${NC}
|
|
168
|
-
${C}/plan${NC}
|
|
169
|
-
${C}/
|
|
170
|
-
${C}/
|
|
171
|
-
${C}/
|
|
244
|
+
${C}/tasks${NC} Show today's tasks
|
|
245
|
+
${C}/plan${NC} Run daily planner
|
|
246
|
+
${C}/agents${NC} List available agents
|
|
247
|
+
${C}/agent <name>${NC} Switch to chatting with a specific agent
|
|
248
|
+
${C}/agent off${NC} Return to NHA Chat
|
|
249
|
+
${C}/create-agent${NC} Create a new custom agent interactively
|
|
250
|
+
${C}/clear${NC} Clear conversation history
|
|
251
|
+
${C}/help${NC} Show this help
|
|
252
|
+
${C}/quit${NC} Exit chat
|
|
172
253
|
|
|
173
|
-
${D}
|
|
254
|
+
${D}You can also type @agent in any message to route it to that agent.
|
|
255
|
+
Example: "@saber audit this function for SQL injection"
|
|
256
|
+
|
|
257
|
+
Otherwise, just type naturally — the AI understands
|
|
174
258
|
requests like "show my unread emails", "add a task to review PR #42",
|
|
175
259
|
"what's on my calendar tomorrow?", "list GitHub issues", etc.${NC}
|
|
176
260
|
`);
|
|
@@ -256,10 +340,32 @@ export async function cmdChat(args) {
|
|
|
256
340
|
}
|
|
257
341
|
|
|
258
342
|
try {
|
|
259
|
-
|
|
343
|
+
// Handle @agent inline routing
|
|
344
|
+
let effectiveSystemPrompt = systemPrompt;
|
|
345
|
+
let effectiveInput = input;
|
|
346
|
+
const atMatch = input.match(/^@(\w+)\s+(.*)/s);
|
|
347
|
+
if (atMatch) {
|
|
348
|
+
const inlineAgent = atMatch[1].toLowerCase();
|
|
349
|
+
const inlinePrompt = atMatch[2];
|
|
350
|
+
const agentFile = path.join(AGENTS_DIR, `${inlineAgent}.mjs`);
|
|
351
|
+
if (fs.existsSync(agentFile)) {
|
|
352
|
+
const agentSource = fs.readFileSync(agentFile, 'utf-8');
|
|
353
|
+
const { card, systemPrompt: agentSysPrompt } = parseAgentFile(agentSource, inlineAgent);
|
|
354
|
+
if (agentSysPrompt) {
|
|
355
|
+
effectiveSystemPrompt = agentSysPrompt;
|
|
356
|
+
effectiveInput = inlinePrompt;
|
|
357
|
+
process.stdout.write(` ${D}Routing to ${card?.displayName || inlineAgent.toUpperCase()}...${NC}\n`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} else if (config._chatAgent) {
|
|
361
|
+
// Persistent agent mode via /agent <name>
|
|
362
|
+
effectiveSystemPrompt = config._chatAgent.systemPrompt;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const userMessage = serializeHistory(history, effectiveInput);
|
|
260
366
|
|
|
261
367
|
process.stdout.write(`\n ${D}Thinking...${NC}`);
|
|
262
|
-
const response = await callLLM(config,
|
|
368
|
+
const response = await callLLM(config, effectiveSystemPrompt, userMessage);
|
|
263
369
|
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
264
370
|
|
|
265
371
|
const { textParts, actions } = parseActions(response);
|