@praesidia/neurogent 0.1.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/dist/chunk-JBDXA3BS.js +214 -0
- package/dist/chunk-VPHKQCPP.js +49 -0
- package/dist/chunk-ZC65T52L.js +255 -0
- package/dist/chunk-ZTZVL6N5.js +108 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +519 -0
- package/dist/index-BW9fPh3v.d.ts +372 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +385 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +8 -0
- package/dist/shell/index.d.ts +1 -0
- package/dist/shell/index.js +889 -0
- package/examples/dev-trio-openai.yaml +42 -0
- package/examples/dev-trio.yaml +42 -0
- package/examples/full-team.yaml +170 -0
- package/examples/marketing-team.yaml +190 -0
- package/examples/solo-researcher.yaml +27 -0
- package/package.json +65 -0
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ShellYamlSchema
|
|
4
|
+
} from "../chunk-VPHKQCPP.js";
|
|
5
|
+
import {
|
|
6
|
+
executeAgent,
|
|
7
|
+
getEffectiveModel
|
|
8
|
+
} from "../chunk-ZC65T52L.js";
|
|
9
|
+
|
|
10
|
+
// src/shell/index.tsx
|
|
11
|
+
import { render } from "ink";
|
|
12
|
+
|
|
13
|
+
// src/shell/App.tsx
|
|
14
|
+
import React, { useReducer, useCallback, useRef } from "react";
|
|
15
|
+
import { Box as Box6, Text as Text6, useApp, useInput } from "ink";
|
|
16
|
+
import TextInput from "ink-text-input";
|
|
17
|
+
import * as fs2 from "fs";
|
|
18
|
+
import * as path2 from "path";
|
|
19
|
+
|
|
20
|
+
// src/shell/components/Header.tsx
|
|
21
|
+
import { Box, Text } from "ink";
|
|
22
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
+
function Header({ shellName, activeCount, totalCount, mode, provider, configPath, width }) {
|
|
24
|
+
return /* @__PURE__ */ jsxs(
|
|
25
|
+
Box,
|
|
26
|
+
{
|
|
27
|
+
flexDirection: "row",
|
|
28
|
+
justifyContent: "space-between",
|
|
29
|
+
borderStyle: "single",
|
|
30
|
+
borderColor: mode === "swarm" ? "yellow" : "magenta",
|
|
31
|
+
paddingX: 1,
|
|
32
|
+
width,
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsxs(Box, { gap: 1, children: [
|
|
35
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u2B21" }),
|
|
36
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: shellName }),
|
|
37
|
+
configPath && /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: `(${configPath.split("/").pop()})` })
|
|
38
|
+
] }),
|
|
39
|
+
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
40
|
+
mode === "swarm" && /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A1 SWARM" }),
|
|
41
|
+
activeCount > 0 && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
42
|
+
"\u25B8 ",
|
|
43
|
+
activeCount,
|
|
44
|
+
" thinking..."
|
|
45
|
+
] }),
|
|
46
|
+
/* @__PURE__ */ jsxs(Text, { color: activeCount > 0 ? "green" : "gray", children: [
|
|
47
|
+
"\u25CF ",
|
|
48
|
+
totalCount,
|
|
49
|
+
" agent",
|
|
50
|
+
totalCount !== 1 ? "s" : ""
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: provider })
|
|
53
|
+
] })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/shell/components/AgentList.tsx
|
|
60
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
61
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
62
|
+
function AgentList({ agents, activeAgents, width }) {
|
|
63
|
+
return /* @__PURE__ */ jsxs2(
|
|
64
|
+
Box2,
|
|
65
|
+
{
|
|
66
|
+
flexDirection: "column",
|
|
67
|
+
width,
|
|
68
|
+
paddingLeft: 1,
|
|
69
|
+
paddingRight: 1,
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "gray", dimColor: true, children: "AGENTS" }),
|
|
72
|
+
/* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
73
|
+
agents.map((agent) => {
|
|
74
|
+
const active = activeAgents.has(agent.id);
|
|
75
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
|
|
76
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
|
|
77
|
+
/* @__PURE__ */ jsx2(Text2, { color: active ? "green" : "gray", children: active ? "\u25CF" : "\u25CB" }),
|
|
78
|
+
/* @__PURE__ */ jsxs2(Text2, { bold: true, color: active ? agent.inkColor : "white", children: [
|
|
79
|
+
agent.emoji,
|
|
80
|
+
" ",
|
|
81
|
+
agent.name
|
|
82
|
+
] })
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx2(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
85
|
+
agent.role,
|
|
86
|
+
active ? " \u25B8" : ""
|
|
87
|
+
] }) })
|
|
88
|
+
] }, agent.id);
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/shell/components/Feed.tsx
|
|
96
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
97
|
+
|
|
98
|
+
// src/shell/components/MessageItem.tsx
|
|
99
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
100
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
101
|
+
function MessageItem({ message, agents }) {
|
|
102
|
+
if (message.type === "user") {
|
|
103
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
104
|
+
/* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
|
|
105
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "white", children: "You" }),
|
|
106
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u203A" })
|
|
107
|
+
] }),
|
|
108
|
+
/* @__PURE__ */ jsx3(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { wrap: "wrap", children: message.content }) })
|
|
109
|
+
] });
|
|
110
|
+
}
|
|
111
|
+
if (message.type === "system") {
|
|
112
|
+
return /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, wrap: "wrap", children: message.content }) });
|
|
113
|
+
}
|
|
114
|
+
if (message.type === "agent" && message.agentId) {
|
|
115
|
+
const agent = agents.find((a) => a.id === message.agentId);
|
|
116
|
+
const color = agent?.inkColor ?? "white";
|
|
117
|
+
const display = message.streaming ? message.content + "\u258C" : message.content;
|
|
118
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
119
|
+
/* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
|
|
120
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color, children: [
|
|
121
|
+
agent?.emoji ?? "\u{1F916}",
|
|
122
|
+
" ",
|
|
123
|
+
agent?.name ?? message.agentId
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u2500" }),
|
|
126
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: agent?.role ?? "Agent" }),
|
|
127
|
+
message.streaming && /* @__PURE__ */ jsx3(Text3, { color: "cyan", dimColor: true, children: "streaming" }),
|
|
128
|
+
!message.streaming && message.cost !== void 0 && message.cost > 0 && /* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
|
|
129
|
+
"$",
|
|
130
|
+
message.cost.toFixed(5)
|
|
131
|
+
] }),
|
|
132
|
+
message.debateRound !== void 0 && /* @__PURE__ */ jsxs3(Text3, { color: "yellow", dimColor: true, children: [
|
|
133
|
+
"round ",
|
|
134
|
+
message.debateRound
|
|
135
|
+
] })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsx3(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { wrap: "wrap", color, children: display }) })
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/shell/components/Feed.tsx
|
|
144
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
145
|
+
var MAX_VISIBLE = 18;
|
|
146
|
+
function Feed({ messages, agents, width, mode, usingDefault }) {
|
|
147
|
+
const visible = messages.slice(-MAX_VISIBLE);
|
|
148
|
+
return /* @__PURE__ */ jsx4(
|
|
149
|
+
Box4,
|
|
150
|
+
{
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
width,
|
|
153
|
+
flexGrow: 1,
|
|
154
|
+
paddingX: 1,
|
|
155
|
+
children: visible.length === 0 && usingDefault ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: 1, children: [
|
|
156
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "magenta", children: "\u2B21 No config found \u2014 using built-in defaults" }),
|
|
157
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
158
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
159
|
+
"Create a ",
|
|
160
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "white", children: "neurogent-shell.yaml" }),
|
|
161
|
+
" in this directory to define your agents:"
|
|
162
|
+
] }),
|
|
163
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
164
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " agents:" }),
|
|
165
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " - id: myagent" }),
|
|
166
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " name: My Agent" }),
|
|
167
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " role: Specialist" }),
|
|
168
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: ' emoji: "\u{1F680}"' }),
|
|
169
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " color: cyan" }),
|
|
170
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " expertise: [keyword1, keyword2]" }),
|
|
171
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: ' system_prompt: "You are..."' }),
|
|
172
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
173
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
174
|
+
"See ",
|
|
175
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "examples/" }),
|
|
176
|
+
" for ready-made configs. Type ",
|
|
177
|
+
/* @__PURE__ */ jsx4(Text4, { color: "white", children: "/help" }),
|
|
178
|
+
" for commands."
|
|
179
|
+
] })
|
|
180
|
+
] }) : visible.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: 1, children: [
|
|
181
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "magenta", children: "\u2B21 Ready" }),
|
|
182
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
183
|
+
"Type a message. Use ",
|
|
184
|
+
/* @__PURE__ */ jsx4(Text4, { color: "white", children: "@agentname" }),
|
|
185
|
+
" to mention a specific agent."
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
188
|
+
"Commands: ",
|
|
189
|
+
/* @__PURE__ */ jsx4(Text4, { color: "white", children: "/swarm /clear /agents /help" })
|
|
190
|
+
] }),
|
|
191
|
+
mode === "swarm" && /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: "\u26A1 Swarm mode active \u2014 all agents will respond" })
|
|
192
|
+
] }) : visible.map((msg) => /* @__PURE__ */ jsx4(MessageItem, { message: msg, agents }, msg.id))
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/shell/components/StatusBar.tsx
|
|
198
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
199
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
200
|
+
function StatusBar({ mode, agentCount, width }) {
|
|
201
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, width, children: [
|
|
202
|
+
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
203
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "@agent" }),
|
|
204
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "/swarm" }),
|
|
205
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "/clear" }),
|
|
206
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "/agents" }),
|
|
207
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "/help" })
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
210
|
+
/* @__PURE__ */ jsx5(Text5, { color: mode === "swarm" ? "yellow" : "gray", dimColor: mode !== "swarm", children: mode === "swarm" ? `\u26A1 swarm (${agentCount} agents)` : "normal" }),
|
|
211
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "ESC quit" })
|
|
212
|
+
] })
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/shell/agents/router.ts
|
|
217
|
+
function extractMentions(input, agents) {
|
|
218
|
+
const mentionRegex = /@(\S+)/g;
|
|
219
|
+
const agentMap = new Map(agents.map((a) => [a.id.toLowerCase(), a.id]));
|
|
220
|
+
const nameMap = new Map(agents.map((a) => [a.name.toLowerCase(), a.id]));
|
|
221
|
+
const results = [];
|
|
222
|
+
let match;
|
|
223
|
+
while ((match = mentionRegex.exec(input)) !== null) {
|
|
224
|
+
const term = match[1].toLowerCase();
|
|
225
|
+
const byId = agentMap.get(term);
|
|
226
|
+
const byName = nameMap.get(term);
|
|
227
|
+
if (byId) results.push(byId);
|
|
228
|
+
else if (byName) results.push(byName);
|
|
229
|
+
}
|
|
230
|
+
return [...new Set(results)];
|
|
231
|
+
}
|
|
232
|
+
function routeMessage(input, agents) {
|
|
233
|
+
const mentions = extractMentions(input, agents);
|
|
234
|
+
if (mentions.length > 0) return mentions;
|
|
235
|
+
const lower = input.toLowerCase();
|
|
236
|
+
const scores = agents.map((agent) => {
|
|
237
|
+
let score = 0;
|
|
238
|
+
for (const kw of agent.expertise) {
|
|
239
|
+
if (kw.length >= 5) {
|
|
240
|
+
if (lower.includes(kw.toLowerCase())) score += 2;
|
|
241
|
+
} else {
|
|
242
|
+
const regex = new RegExp(`\\b${kw.toLowerCase()}\\b`);
|
|
243
|
+
if (regex.test(lower)) score += 3;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return [agent.id, score];
|
|
247
|
+
});
|
|
248
|
+
const positive = scores.filter(([, s]) => s > 0).sort((a, b) => b[1] - a[1]);
|
|
249
|
+
if (positive.length === 0) {
|
|
250
|
+
return [agents[0].id];
|
|
251
|
+
}
|
|
252
|
+
const topScore = positive[0][1];
|
|
253
|
+
return positive.filter(([, s]) => s >= topScore * 0.75).slice(0, 2).map(([id]) => id);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/shell/session.ts
|
|
257
|
+
import * as fs from "fs";
|
|
258
|
+
import * as path from "path";
|
|
259
|
+
var SESSION_FILE = ".neurogent-session.json";
|
|
260
|
+
function saveSession(history) {
|
|
261
|
+
if (history.length === 0) return;
|
|
262
|
+
try {
|
|
263
|
+
const data = { savedAt: (/* @__PURE__ */ new Date()).toISOString(), history: history.slice(-50) };
|
|
264
|
+
fs.writeFileSync(path.resolve(process.cwd(), SESSION_FILE), JSON.stringify(data, null, 2), "utf-8");
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function loadSession() {
|
|
269
|
+
try {
|
|
270
|
+
const filePath = path.resolve(process.cwd(), SESSION_FILE);
|
|
271
|
+
if (!fs.existsSync(filePath)) return [];
|
|
272
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
273
|
+
return data.history ?? [];
|
|
274
|
+
} catch {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function clearSession() {
|
|
279
|
+
try {
|
|
280
|
+
const filePath = path.resolve(process.cwd(), SESSION_FILE);
|
|
281
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/shell/App.tsx
|
|
287
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
288
|
+
function init() {
|
|
289
|
+
return { messages: [], activeAgents: /* @__PURE__ */ new Set(), mode: "normal" };
|
|
290
|
+
}
|
|
291
|
+
function reducer(state, action) {
|
|
292
|
+
switch (action.type) {
|
|
293
|
+
case "ADD":
|
|
294
|
+
return { ...state, messages: [...state.messages.slice(-100), action.msg] };
|
|
295
|
+
case "UPDATE":
|
|
296
|
+
return {
|
|
297
|
+
...state,
|
|
298
|
+
messages: state.messages.map(
|
|
299
|
+
(m) => m.id === action.id ? { ...m, content: action.content, streaming: !action.done } : m
|
|
300
|
+
)
|
|
301
|
+
};
|
|
302
|
+
case "AGENT_ON": {
|
|
303
|
+
const s = new Set(state.activeAgents);
|
|
304
|
+
s.add(action.id);
|
|
305
|
+
return { ...state, activeAgents: s };
|
|
306
|
+
}
|
|
307
|
+
case "AGENT_OFF": {
|
|
308
|
+
const s = new Set(state.activeAgents);
|
|
309
|
+
s.delete(action.id);
|
|
310
|
+
return { ...state, activeAgents: s };
|
|
311
|
+
}
|
|
312
|
+
case "SET_MODE":
|
|
313
|
+
return { ...state, mode: action.mode };
|
|
314
|
+
case "SET_COST":
|
|
315
|
+
return {
|
|
316
|
+
...state,
|
|
317
|
+
messages: state.messages.map(
|
|
318
|
+
(m) => m.id === action.id ? { ...m, cost: action.cost } : m
|
|
319
|
+
)
|
|
320
|
+
};
|
|
321
|
+
case "CLEAR":
|
|
322
|
+
return { ...state, messages: [] };
|
|
323
|
+
default:
|
|
324
|
+
return state;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function App({ config, configPath, usingDefault }) {
|
|
328
|
+
const { exit } = useApp();
|
|
329
|
+
const [state, dispatch] = useReducer(reducer, void 0, init);
|
|
330
|
+
const [inputValue, setInputValue] = React.useState("");
|
|
331
|
+
const historyRef = useRef(loadSession());
|
|
332
|
+
const modeRef = useRef("normal");
|
|
333
|
+
const stateRef = useRef(state);
|
|
334
|
+
React.useEffect(() => {
|
|
335
|
+
modeRef.current = state.mode;
|
|
336
|
+
}, [state.mode]);
|
|
337
|
+
React.useEffect(() => {
|
|
338
|
+
stateRef.current = state;
|
|
339
|
+
}, [state]);
|
|
340
|
+
useInput((_ch, key) => {
|
|
341
|
+
if (key.escape || key.ctrl && _ch === "c") exit();
|
|
342
|
+
});
|
|
343
|
+
const addSystem = useCallback((content) => {
|
|
344
|
+
dispatch({
|
|
345
|
+
type: "ADD",
|
|
346
|
+
msg: { id: crypto.randomUUID(), type: "system", content, streaming: false, timestamp: Date.now() }
|
|
347
|
+
});
|
|
348
|
+
}, []);
|
|
349
|
+
React.useEffect(() => {
|
|
350
|
+
const loaded = historyRef.current;
|
|
351
|
+
if (loaded.length > 0) {
|
|
352
|
+
addSystem(`\u21A9 Restored ${loaded.length} messages from previous session. Type /clear to start fresh.`);
|
|
353
|
+
}
|
|
354
|
+
}, []);
|
|
355
|
+
const streamFromAgent = useCallback((agentId, userMessage) => {
|
|
356
|
+
const msgId = crypto.randomUUID();
|
|
357
|
+
const agent = config.agents.find((a) => a.id === agentId);
|
|
358
|
+
if (!agent) return;
|
|
359
|
+
dispatch({ type: "ADD", msg: { id: msgId, type: "agent", agentId, content: "", streaming: true, timestamp: Date.now() } });
|
|
360
|
+
dispatch({ type: "AGENT_ON", id: agentId });
|
|
361
|
+
void (async () => {
|
|
362
|
+
let text = "";
|
|
363
|
+
try {
|
|
364
|
+
for await (const chunk of executeAgent(agent, userMessage, historyRef.current, config.globalModel, (cost) => {
|
|
365
|
+
dispatch({ type: "SET_COST", id: msgId, cost });
|
|
366
|
+
})) {
|
|
367
|
+
text += chunk;
|
|
368
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: false });
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
text += `
|
|
372
|
+
\u26A0 ${err instanceof Error ? err.message : String(err)}`;
|
|
373
|
+
} finally {
|
|
374
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: true });
|
|
375
|
+
dispatch({ type: "AGENT_OFF", id: agentId });
|
|
376
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "assistant", content: text }];
|
|
377
|
+
saveSession(historyRef.current);
|
|
378
|
+
}
|
|
379
|
+
})();
|
|
380
|
+
}, [config]);
|
|
381
|
+
const streamFromAgentAsync = useCallback((agentId, userMessage) => {
|
|
382
|
+
const msgId = crypto.randomUUID();
|
|
383
|
+
const agent = config.agents.find((a) => a.id === agentId);
|
|
384
|
+
if (!agent) return Promise.resolve("");
|
|
385
|
+
dispatch({ type: "ADD", msg: { id: msgId, type: "agent", agentId, content: "", streaming: true, timestamp: Date.now() } });
|
|
386
|
+
dispatch({ type: "AGENT_ON", id: agentId });
|
|
387
|
+
return (async () => {
|
|
388
|
+
let text = "";
|
|
389
|
+
try {
|
|
390
|
+
for await (const chunk of executeAgent(agent, userMessage, historyRef.current, config.globalModel, (cost) => {
|
|
391
|
+
dispatch({ type: "SET_COST", id: msgId, cost });
|
|
392
|
+
})) {
|
|
393
|
+
text += chunk;
|
|
394
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: false });
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
text += `
|
|
398
|
+
\u26A0 ${err instanceof Error ? err.message : String(err)}`;
|
|
399
|
+
} finally {
|
|
400
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: true });
|
|
401
|
+
dispatch({ type: "AGENT_OFF", id: agentId });
|
|
402
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "assistant", content: text }];
|
|
403
|
+
saveSession(historyRef.current);
|
|
404
|
+
}
|
|
405
|
+
return text;
|
|
406
|
+
})();
|
|
407
|
+
}, [config]);
|
|
408
|
+
const handleCommand = useCallback((cmd) => {
|
|
409
|
+
const [base] = cmd.trim().toLowerCase().split(/\s+/);
|
|
410
|
+
switch (base) {
|
|
411
|
+
case "/clear":
|
|
412
|
+
dispatch({ type: "CLEAR" });
|
|
413
|
+
clearSession();
|
|
414
|
+
historyRef.current = [];
|
|
415
|
+
break;
|
|
416
|
+
case "/swarm": {
|
|
417
|
+
const next = modeRef.current === "swarm" ? "normal" : "swarm";
|
|
418
|
+
dispatch({ type: "SET_MODE", mode: next });
|
|
419
|
+
addSystem(next === "swarm" ? `\u26A1 Swarm mode ON \u2014 all ${config.agents.length} agents will respond` : "\u2713 Swarm mode OFF \u2014 smart routing restored");
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case "/debate":
|
|
423
|
+
addSystem('\u2694 Debate mode: use "@agent1 vs @agent2 <topic>" to start a debate between two agents (3 rounds)');
|
|
424
|
+
break;
|
|
425
|
+
case "/agents":
|
|
426
|
+
addSystem(config.agents.map((a) => `${a.emoji} ${a.name} (${a.role}) \u2014 ${a.expertise.slice(0, 4).join(", ")}`).join("\n"));
|
|
427
|
+
break;
|
|
428
|
+
case "/export": {
|
|
429
|
+
const msgs = stateRef.current.messages;
|
|
430
|
+
if (msgs.length === 0) {
|
|
431
|
+
addSystem("Nothing to export \u2014 conversation is empty.");
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
const now = /* @__PURE__ */ new Date();
|
|
435
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
436
|
+
const filename = `neurogent-session-${ts}.md`;
|
|
437
|
+
const exportPath = path2.resolve(process.cwd(), filename);
|
|
438
|
+
const configLabel = configPath ? path2.basename(configPath) : "built-in default";
|
|
439
|
+
const dateLabel = now.toISOString().replace("T", " ").slice(0, 19);
|
|
440
|
+
const lines = [
|
|
441
|
+
"# Neuro Shell Session",
|
|
442
|
+
`*Exported: ${dateLabel}*`,
|
|
443
|
+
`*Config: ${configLabel}*`,
|
|
444
|
+
""
|
|
445
|
+
];
|
|
446
|
+
for (const msg of msgs) {
|
|
447
|
+
if (msg.type === "user") {
|
|
448
|
+
lines.push("---", "", `**You:** ${msg.content}`, "");
|
|
449
|
+
} else if (msg.type === "agent") {
|
|
450
|
+
const agent = config.agents.find((a) => a.id === msg.agentId);
|
|
451
|
+
const label = agent ? `${agent.emoji} ${agent.name} (${agent.role})` : msg.agentId ?? "Agent";
|
|
452
|
+
lines.push("---", "", `**${label}:**`, msg.content, "");
|
|
453
|
+
} else {
|
|
454
|
+
lines.push("---", "", `*${msg.content}*`, "");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
lines.push("---");
|
|
458
|
+
try {
|
|
459
|
+
fs2.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
460
|
+
addSystem(`\u2713 Session exported to ${exportPath}`);
|
|
461
|
+
} catch (err) {
|
|
462
|
+
addSystem(`\u26A0 Export failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "/help":
|
|
467
|
+
addSystem([
|
|
468
|
+
"Commands: /swarm /clear /agents /export /debate /help",
|
|
469
|
+
"Chain: @nova >> @orion (pipe response between agents)",
|
|
470
|
+
"Debate: @nova vs @orion <topic> (3-round structured debate with synthesis)",
|
|
471
|
+
"Bench: @nova @orion <question> (run same question through multiple agents)",
|
|
472
|
+
"Files: @file:src/index.ts (inject file contents into message)",
|
|
473
|
+
"Git: git context auto-injected when inside a repo",
|
|
474
|
+
`Mentions: ${config.agents.map((a) => `@${a.id}`).join(" ")}`,
|
|
475
|
+
`Config: ${configPath ?? "built-in default"}`,
|
|
476
|
+
`Provider: set ANTHROPIC_API_KEY, OPENAI_API_KEY, or OLLAMA_BASE_URL`
|
|
477
|
+
].join("\n"));
|
|
478
|
+
break;
|
|
479
|
+
default:
|
|
480
|
+
addSystem(`Unknown command: ${cmd}. Try /help`);
|
|
481
|
+
}
|
|
482
|
+
}, [config, configPath, addSystem]);
|
|
483
|
+
const handleSubmit = useCallback((value) => {
|
|
484
|
+
const trimmed = value.trim();
|
|
485
|
+
if (!trimmed) return;
|
|
486
|
+
setInputValue("");
|
|
487
|
+
if (trimmed.startsWith("/")) {
|
|
488
|
+
handleCommand(trimmed);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const debateMatch = trimmed.match(/^@(\w+)\s+vs\s+@(\w+)\s+(.+)$/i);
|
|
492
|
+
if (debateMatch) {
|
|
493
|
+
const [, id1, id2, topic] = debateMatch;
|
|
494
|
+
const agent1 = config.agents.find((a) => a.id === id1);
|
|
495
|
+
const agent2 = config.agents.find((a) => a.id === id2);
|
|
496
|
+
if (agent1 && agent2) {
|
|
497
|
+
dispatch({ type: "ADD", msg: { id: crypto.randomUUID(), type: "user", content: trimmed, streaming: false, timestamp: Date.now() } });
|
|
498
|
+
addSystem(`\u2694 Debate: ${agent1.emoji} ${agent1.name} vs ${agent2.emoji} ${agent2.name} \u2014 "${topic}" (3 rounds)`);
|
|
499
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "user", content: trimmed }];
|
|
500
|
+
saveSession(historyRef.current);
|
|
501
|
+
void (async () => {
|
|
502
|
+
const ROUNDS = 3;
|
|
503
|
+
let lastMessage = topic;
|
|
504
|
+
for (let round = 1; round <= ROUNDS; round++) {
|
|
505
|
+
addSystem(`\u2500\u2500\u2500 Round ${round} \u2500\u2500\u2500`);
|
|
506
|
+
const msgId1 = crypto.randomUUID();
|
|
507
|
+
dispatch({ type: "ADD", msg: { id: msgId1, type: "agent", agentId: agent1.id, content: "", streaming: true, timestamp: Date.now(), debateRound: round } });
|
|
508
|
+
dispatch({ type: "AGENT_ON", id: agent1.id });
|
|
509
|
+
let reply1 = "";
|
|
510
|
+
const prompt1 = round === 1 ? `You are in a structured debate. Topic: "${topic}". Present your position clearly and persuasively in under 150 words.` : `You are in a structured debate. Topic: "${topic}". ${agent2.name} said: "${lastMessage}". Respond to their argument and defend your position in under 150 words.`;
|
|
511
|
+
for await (const chunk of executeAgent(agent1, prompt1, [], config.globalModel)) {
|
|
512
|
+
reply1 += chunk;
|
|
513
|
+
dispatch({ type: "UPDATE", id: msgId1, content: reply1, done: false });
|
|
514
|
+
}
|
|
515
|
+
dispatch({ type: "UPDATE", id: msgId1, content: reply1, done: true });
|
|
516
|
+
dispatch({ type: "AGENT_OFF", id: agent1.id });
|
|
517
|
+
const msgId2 = crypto.randomUUID();
|
|
518
|
+
dispatch({ type: "ADD", msg: { id: msgId2, type: "agent", agentId: agent2.id, content: "", streaming: true, timestamp: Date.now(), debateRound: round } });
|
|
519
|
+
dispatch({ type: "AGENT_ON", id: agent2.id });
|
|
520
|
+
let reply2 = "";
|
|
521
|
+
const prompt2 = `You are in a structured debate. Topic: "${topic}". ${agent1.name} said: "${reply1}". Counter their argument from your expert perspective in under 150 words.`;
|
|
522
|
+
for await (const chunk of executeAgent(agent2, prompt2, [], config.globalModel)) {
|
|
523
|
+
reply2 += chunk;
|
|
524
|
+
dispatch({ type: "UPDATE", id: msgId2, content: reply2, done: false });
|
|
525
|
+
}
|
|
526
|
+
dispatch({ type: "UPDATE", id: msgId2, content: reply2, done: true });
|
|
527
|
+
dispatch({ type: "AGENT_OFF", id: agent2.id });
|
|
528
|
+
lastMessage = reply2;
|
|
529
|
+
}
|
|
530
|
+
addSystem(`\u2500\u2500\u2500 Synthesis \u2500\u2500\u2500`);
|
|
531
|
+
const synthId = crypto.randomUUID();
|
|
532
|
+
dispatch({ type: "ADD", msg: { id: synthId, type: "system", content: "...synthesizing consensus...", streaming: false, timestamp: Date.now() } });
|
|
533
|
+
const synthMsgId = crypto.randomUUID();
|
|
534
|
+
dispatch({ type: "ADD", msg: { id: synthMsgId, type: "agent", agentId: agent1.id, content: "", streaming: true, timestamp: Date.now() } });
|
|
535
|
+
dispatch({ type: "AGENT_ON", id: agent1.id });
|
|
536
|
+
let synthesis = "";
|
|
537
|
+
const synthPrompt = `You just debated "${topic}" against ${agent2.name}. Find 3 key points of consensus and 1 remaining disagreement. Be brief and concrete.`;
|
|
538
|
+
for await (const chunk of executeAgent(agent1, synthPrompt, [], config.globalModel)) {
|
|
539
|
+
synthesis += chunk;
|
|
540
|
+
dispatch({ type: "UPDATE", id: synthMsgId, content: synthesis, done: false });
|
|
541
|
+
}
|
|
542
|
+
dispatch({ type: "UPDATE", id: synthMsgId, content: synthesis, done: true });
|
|
543
|
+
dispatch({ type: "AGENT_OFF", id: agent1.id });
|
|
544
|
+
})();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const mentionMatches = trimmed.match(/@(\w+)/g);
|
|
549
|
+
const isBenchmark = mentionMatches && mentionMatches.length >= 2 && !trimmed.includes(">>") && !debateMatch;
|
|
550
|
+
if (isBenchmark) {
|
|
551
|
+
const agentIds = mentionMatches.map((m) => m.slice(1));
|
|
552
|
+
const validAgents = agentIds.map((id) => config.agents.find((a) => a.id === id)).filter(Boolean);
|
|
553
|
+
if (validAgents.length >= 2) {
|
|
554
|
+
const question = trimmed.replace(/@\w+/g, "").trim();
|
|
555
|
+
dispatch({ type: "ADD", msg: { id: crypto.randomUUID(), type: "user", content: trimmed, streaming: false, timestamp: Date.now() } });
|
|
556
|
+
addSystem(`\u26A1 Benchmarking ${validAgents.length} agents on: "${question.slice(0, 60)}${question.length > 60 ? "..." : ""}"`);
|
|
557
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "user", content: trimmed }];
|
|
558
|
+
saveSession(historyRef.current);
|
|
559
|
+
const startTime = Date.now();
|
|
560
|
+
let totalCost = 0;
|
|
561
|
+
let completed = 0;
|
|
562
|
+
for (const agent of validAgents) {
|
|
563
|
+
const msgId = crypto.randomUUID();
|
|
564
|
+
dispatch({ type: "ADD", msg: { id: msgId, type: "agent", agentId: agent.id, content: "", streaming: true, timestamp: Date.now() } });
|
|
565
|
+
dispatch({ type: "AGENT_ON", id: agent.id });
|
|
566
|
+
void (async () => {
|
|
567
|
+
let text = "";
|
|
568
|
+
try {
|
|
569
|
+
for await (const chunk of executeAgent(agent, question, historyRef.current, config.globalModel, (cost) => {
|
|
570
|
+
totalCost += cost;
|
|
571
|
+
dispatch({ type: "SET_COST", id: msgId, cost });
|
|
572
|
+
})) {
|
|
573
|
+
text += chunk;
|
|
574
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: false });
|
|
575
|
+
}
|
|
576
|
+
} catch (err) {
|
|
577
|
+
text += `
|
|
578
|
+
\u26A0 ${err instanceof Error ? err.message : String(err)}`;
|
|
579
|
+
} finally {
|
|
580
|
+
dispatch({ type: "UPDATE", id: msgId, content: text, done: true });
|
|
581
|
+
dispatch({ type: "AGENT_OFF", id: agent.id });
|
|
582
|
+
completed++;
|
|
583
|
+
if (completed === validAgents.length) {
|
|
584
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
585
|
+
addSystem(`\u2713 Benchmark complete \u2014 ${validAgents.length} responses in ${elapsed}s \xB7 total $${totalCost.toFixed(5)}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
})();
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (trimmed.includes(" >> ")) {
|
|
594
|
+
const parts = trimmed.split(" >> ");
|
|
595
|
+
const firstPart = parts[0].trim();
|
|
596
|
+
const mentionMatch = firstPart.match(/^(.*?)(@\w+)\s*$/);
|
|
597
|
+
let userMessage;
|
|
598
|
+
let chainIds;
|
|
599
|
+
if (mentionMatch) {
|
|
600
|
+
userMessage = mentionMatch[1].trim() || firstPart.replace(/@\w+/, "").trim() || firstPart;
|
|
601
|
+
chainIds = [mentionMatch[2].slice(1), ...parts.slice(1).map((p) => {
|
|
602
|
+
const m = p.trim().match(/@(\w+)/);
|
|
603
|
+
return m ? m[1] : p.trim();
|
|
604
|
+
})];
|
|
605
|
+
} else {
|
|
606
|
+
userMessage = firstPart;
|
|
607
|
+
chainIds = parts.slice(1).map((p) => {
|
|
608
|
+
const m = p.trim().match(/@(\w+)/);
|
|
609
|
+
return m ? m[1] : p.trim();
|
|
610
|
+
});
|
|
611
|
+
const firstMention = firstPart.match(/^@(\w+)$/);
|
|
612
|
+
if (firstMention) {
|
|
613
|
+
chainIds = [firstMention[1], ...chainIds];
|
|
614
|
+
userMessage = "";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const validChainIds = chainIds.filter((id) => config.agents.some((a) => a.id === id));
|
|
618
|
+
if (validChainIds.length < 2) {
|
|
619
|
+
} else {
|
|
620
|
+
const displayMessage = userMessage || trimmed;
|
|
621
|
+
dispatch({ type: "ADD", msg: { id: crypto.randomUUID(), type: "user", content: displayMessage, streaming: false, timestamp: Date.now() } });
|
|
622
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "user", content: displayMessage }];
|
|
623
|
+
saveSession(historyRef.current);
|
|
624
|
+
addSystem(`\u26D3 Chain mode: ${validChainIds.map((id) => `@${id}`).join(" \u2192 ")}`);
|
|
625
|
+
void (async () => {
|
|
626
|
+
let currentInput = userMessage || displayMessage;
|
|
627
|
+
for (const agentId of validChainIds) {
|
|
628
|
+
currentInput = await streamFromAgentAsync(agentId, currentInput);
|
|
629
|
+
}
|
|
630
|
+
})();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
dispatch({ type: "ADD", msg: { id: crypto.randomUUID(), type: "user", content: trimmed, streaming: false, timestamp: Date.now() } });
|
|
635
|
+
historyRef.current = [...historyRef.current.slice(-20), { role: "user", content: trimmed }];
|
|
636
|
+
saveSession(historyRef.current);
|
|
637
|
+
const targets = modeRef.current === "swarm" ? config.agents.map((a) => a.id) : routeMessage(trimmed, config.agents);
|
|
638
|
+
for (const agentId of targets) {
|
|
639
|
+
streamFromAgent(agentId, trimmed);
|
|
640
|
+
}
|
|
641
|
+
}, [config, handleCommand, streamFromAgent, streamFromAgentAsync, addSystem]);
|
|
642
|
+
const sampleModel = getEffectiveModel(config.agents[0], config.globalModel);
|
|
643
|
+
const providerLabel = `${sampleModel.provider}/${sampleModel.name.split("-").slice(0, 2).join("-")}`;
|
|
644
|
+
const termWidthRef = useRef(process.stdout.columns ?? 100);
|
|
645
|
+
const termHeightRef = useRef(process.stdout.rows ?? 24);
|
|
646
|
+
const termWidth = termWidthRef.current;
|
|
647
|
+
const termHeight = termHeightRef.current;
|
|
648
|
+
const agentPanelWidth = Math.min(22, Math.floor(termWidth * 0.2));
|
|
649
|
+
const feedWidth = termWidth - agentPanelWidth;
|
|
650
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: termWidth, height: termHeight, children: [
|
|
651
|
+
/* @__PURE__ */ jsx6(
|
|
652
|
+
Header,
|
|
653
|
+
{
|
|
654
|
+
shellName: config.shellName,
|
|
655
|
+
activeCount: state.activeAgents.size,
|
|
656
|
+
totalCount: config.agents.length,
|
|
657
|
+
mode: state.mode,
|
|
658
|
+
provider: providerLabel,
|
|
659
|
+
configPath,
|
|
660
|
+
width: termWidth
|
|
661
|
+
}
|
|
662
|
+
),
|
|
663
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", flexGrow: 1, children: [
|
|
664
|
+
/* @__PURE__ */ jsx6(AgentList, { agents: config.agents, activeAgents: state.activeAgents, width: agentPanelWidth }),
|
|
665
|
+
/* @__PURE__ */ jsx6(Feed, { messages: state.messages, agents: config.agents, width: feedWidth, mode: state.mode, usingDefault })
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsxs6(Box6, { borderStyle: "single", borderColor: "gray", paddingX: 1, width: termWidth, children: [
|
|
668
|
+
/* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "\u203A " }),
|
|
669
|
+
/* @__PURE__ */ jsx6(
|
|
670
|
+
TextInput,
|
|
671
|
+
{
|
|
672
|
+
value: inputValue,
|
|
673
|
+
onChange: setInputValue,
|
|
674
|
+
onSubmit: handleSubmit,
|
|
675
|
+
placeholder: `Message your agents... (@${config.agents[0]?.id ?? "agent"}, /swarm, /help)`
|
|
676
|
+
}
|
|
677
|
+
)
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ jsx6(StatusBar, { mode: state.mode, agentCount: config.agents.length, width: termWidth })
|
|
680
|
+
] });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/shell/config/loader.ts
|
|
684
|
+
import * as fs3 from "fs";
|
|
685
|
+
import * as path3 from "path";
|
|
686
|
+
import * as yaml from "js-yaml";
|
|
687
|
+
|
|
688
|
+
// src/shell/config/defaults.ts
|
|
689
|
+
var DEFAULT_CONFIG = {
|
|
690
|
+
shellName: "Neuro Shell",
|
|
691
|
+
globalModel: {
|
|
692
|
+
provider: process.env.ANTHROPIC_API_KEY ? "anthropic" : "openai",
|
|
693
|
+
name: process.env.ANTHROPIC_API_KEY ? "claude-3-5-sonnet-20241022" : "gpt-4o"
|
|
694
|
+
},
|
|
695
|
+
agents: [
|
|
696
|
+
{
|
|
697
|
+
id: "assistant",
|
|
698
|
+
name: "Assistant",
|
|
699
|
+
role: "General",
|
|
700
|
+
emoji: "\u{1F916}",
|
|
701
|
+
inkColor: "cyan",
|
|
702
|
+
expertise: ["help", "question", "explain", "what", "how", "why", "tell me"],
|
|
703
|
+
systemPrompt: "You are a helpful, knowledgeable assistant. Be concise and clear. Format responses for the terminal with minimal markdown."
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
id: "coder",
|
|
707
|
+
name: "Coder",
|
|
708
|
+
role: "Engineer",
|
|
709
|
+
emoji: "\u26A1",
|
|
710
|
+
inkColor: "green",
|
|
711
|
+
expertise: ["code", "debug", "function", "class", "bug", "error", "typescript", "python", "javascript", "refactor", "review"],
|
|
712
|
+
systemPrompt: "You are an elite software engineer. Write clean, efficient code. Always explain your reasoning briefly. Use markdown code blocks with proper language tags."
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
// src/shell/config/loader.ts
|
|
718
|
+
var CONFIG_SEARCH_PATHS = [
|
|
719
|
+
"neurogent-shell.yaml",
|
|
720
|
+
"neurogent-shell.yml",
|
|
721
|
+
".neurogent-shell.yaml",
|
|
722
|
+
".neurogent-shell.yml"
|
|
723
|
+
];
|
|
724
|
+
function resolveGlobalPath() {
|
|
725
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
726
|
+
return path3.join(home, ".neuro", "shell.yaml");
|
|
727
|
+
}
|
|
728
|
+
function parseYamlConfig(filePath) {
|
|
729
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
730
|
+
const parsed = yaml.load(raw);
|
|
731
|
+
const validated = ShellYamlSchema.parse(parsed);
|
|
732
|
+
const globalModel = validated.model ? {
|
|
733
|
+
provider: validated.model.provider,
|
|
734
|
+
name: validated.model.name,
|
|
735
|
+
maxTokens: validated.model.max_tokens,
|
|
736
|
+
temperature: validated.model.temperature,
|
|
737
|
+
baseUrl: validated.model.base_url
|
|
738
|
+
} : void 0;
|
|
739
|
+
const agents = validated.agents.map((a) => ({
|
|
740
|
+
id: a.id,
|
|
741
|
+
name: a.name,
|
|
742
|
+
role: a.role,
|
|
743
|
+
emoji: a.emoji,
|
|
744
|
+
inkColor: a.color,
|
|
745
|
+
expertise: a.expertise,
|
|
746
|
+
systemPrompt: a.system_prompt,
|
|
747
|
+
model: a.model ? {
|
|
748
|
+
provider: a.model.provider,
|
|
749
|
+
name: a.model.name,
|
|
750
|
+
maxTokens: a.model.max_tokens,
|
|
751
|
+
temperature: a.model.temperature,
|
|
752
|
+
baseUrl: a.model.base_url
|
|
753
|
+
} : void 0
|
|
754
|
+
}));
|
|
755
|
+
return {
|
|
756
|
+
shellName: validated.shell.name,
|
|
757
|
+
globalModel,
|
|
758
|
+
agents
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function loadConfig(explicitPath) {
|
|
762
|
+
if (explicitPath) {
|
|
763
|
+
const resolved = path3.resolve(explicitPath);
|
|
764
|
+
if (!fs3.existsSync(resolved)) {
|
|
765
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
766
|
+
}
|
|
767
|
+
return { config: parseYamlConfig(resolved), configPath: resolved, usingDefault: false };
|
|
768
|
+
}
|
|
769
|
+
for (const name of CONFIG_SEARCH_PATHS) {
|
|
770
|
+
const resolved = path3.resolve(name);
|
|
771
|
+
if (fs3.existsSync(resolved)) {
|
|
772
|
+
return { config: parseYamlConfig(resolved), configPath: resolved, usingDefault: false };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const globalPath = resolveGlobalPath();
|
|
776
|
+
if (fs3.existsSync(globalPath)) {
|
|
777
|
+
return { config: parseYamlConfig(globalPath), configPath: globalPath, usingDefault: false };
|
|
778
|
+
}
|
|
779
|
+
return { config: DEFAULT_CONFIG, configPath: null, usingDefault: true };
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/shell/packs/installer.ts
|
|
783
|
+
import * as fs4 from "fs";
|
|
784
|
+
import * as path4 from "path";
|
|
785
|
+
import * as url from "url";
|
|
786
|
+
var BUILTIN_PACKS = {
|
|
787
|
+
"full-team": "full-team.yaml",
|
|
788
|
+
"dev-trio": "dev-trio.yaml",
|
|
789
|
+
"solo-researcher": "solo-researcher.yaml"
|
|
790
|
+
};
|
|
791
|
+
function listBuiltinPacks() {
|
|
792
|
+
return [
|
|
793
|
+
{ name: "full-team", description: "10 specialized agents: coder, cloud, researcher, writer, security, data, marketer, devops, PM, support", agentCount: 10, source: "builtin" },
|
|
794
|
+
{ name: "dev-trio", description: "3-agent engineering team: full-stack engineer, security specialist, code reviewer", agentCount: 3, source: "builtin" },
|
|
795
|
+
{ name: "solo-researcher", description: "Single deep research agent for exploration and analysis sessions", agentCount: 1, source: "builtin" }
|
|
796
|
+
];
|
|
797
|
+
}
|
|
798
|
+
function installPack(packName, targetDir = ".") {
|
|
799
|
+
const filename = BUILTIN_PACKS[packName];
|
|
800
|
+
if (!filename) {
|
|
801
|
+
const available = Object.keys(BUILTIN_PACKS).join(", ");
|
|
802
|
+
throw new Error(`Unknown pack "${packName}". Available: ${available}`);
|
|
803
|
+
}
|
|
804
|
+
const __dirname2 = path4.dirname(url.fileURLToPath(import.meta.url));
|
|
805
|
+
const packageRoot = path4.resolve(__dirname2, "..", "..", "..");
|
|
806
|
+
const sourcePath = path4.join(packageRoot, "examples", filename);
|
|
807
|
+
if (!fs4.existsSync(sourcePath)) {
|
|
808
|
+
throw new Error(`Pack file not found: ${sourcePath}`);
|
|
809
|
+
}
|
|
810
|
+
const destPath = path4.resolve(targetDir, "neurogent-shell.yaml");
|
|
811
|
+
const content = fs4.readFileSync(sourcePath, "utf-8");
|
|
812
|
+
fs4.writeFileSync(destPath, content, "utf-8");
|
|
813
|
+
return destPath;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/shell/index.tsx
|
|
817
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
818
|
+
var args = process.argv.slice(2);
|
|
819
|
+
var subcommand = args[0];
|
|
820
|
+
if (subcommand === "packs") {
|
|
821
|
+
const packs = listBuiltinPacks();
|
|
822
|
+
console.log("\nAvailable built-in packs:\n");
|
|
823
|
+
for (const pack of packs) {
|
|
824
|
+
console.log(` ${pack.name}`);
|
|
825
|
+
console.log(` ${pack.description}`);
|
|
826
|
+
console.log(` Agents: ${pack.agentCount} | Source: ${pack.source}
|
|
827
|
+
`);
|
|
828
|
+
}
|
|
829
|
+
console.log("Install a pack: neurogent-shell install <pack-name>\n");
|
|
830
|
+
process.exit(0);
|
|
831
|
+
}
|
|
832
|
+
if (subcommand === "install") {
|
|
833
|
+
const packName = args[1];
|
|
834
|
+
if (!packName) {
|
|
835
|
+
console.error("Usage: neurogent-shell install <pack-name>");
|
|
836
|
+
console.error("Run `neurogent-shell packs` to see available packs.");
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
const destPath = installPack(packName, process.cwd());
|
|
841
|
+
console.log(`
|
|
842
|
+
\u2713 Pack "${packName}" installed to ${destPath}`);
|
|
843
|
+
console.log(`
|
|
844
|
+
Start your session:
|
|
845
|
+
neurogent-shell --config ${destPath}
|
|
846
|
+
`);
|
|
847
|
+
process.exit(0);
|
|
848
|
+
} catch (err) {
|
|
849
|
+
console.error(`\u274C Install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
850
|
+
process.exit(1);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
var configFlagIdx = args.findIndex((a) => a === "--config" || a === "-c");
|
|
854
|
+
var explicitConfig = configFlagIdx !== -1 ? args[configFlagIdx + 1] : void 0;
|
|
855
|
+
var loadResult;
|
|
856
|
+
try {
|
|
857
|
+
loadResult = loadConfig(explicitConfig);
|
|
858
|
+
} catch (err) {
|
|
859
|
+
console.error("\u274C Config error:", err instanceof Error ? err.message : String(err));
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
process.stdout.write("\x1B[?1049h");
|
|
863
|
+
process.stdout.write("\x1B[?25l");
|
|
864
|
+
var restore = () => {
|
|
865
|
+
process.stdout.write("\x1B[?25h");
|
|
866
|
+
process.stdout.write("\x1B[?1049l");
|
|
867
|
+
};
|
|
868
|
+
process.on("exit", restore);
|
|
869
|
+
process.on("SIGTERM", () => {
|
|
870
|
+
restore();
|
|
871
|
+
process.exit(0);
|
|
872
|
+
});
|
|
873
|
+
process.on("SIGINT", () => {
|
|
874
|
+
restore();
|
|
875
|
+
process.exit(0);
|
|
876
|
+
});
|
|
877
|
+
var { waitUntilExit } = render(
|
|
878
|
+
/* @__PURE__ */ jsx7(
|
|
879
|
+
App,
|
|
880
|
+
{
|
|
881
|
+
config: loadResult.config,
|
|
882
|
+
configPath: loadResult.configPath,
|
|
883
|
+
usingDefault: loadResult.usingDefault
|
|
884
|
+
}
|
|
885
|
+
),
|
|
886
|
+
{ exitOnCtrlC: false, patchConsole: true }
|
|
887
|
+
);
|
|
888
|
+
await waitUntilExit();
|
|
889
|
+
restore();
|