neuron-inspector 0.1.1 → 0.2.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/README.md +145 -86
- package/dist/recipe-tools.d.ts +8 -0
- package/dist/recipe-tools.js +169 -0
- package/dist/recipe-tools.js.map +1 -0
- package/dist/recipes.d.ts +57 -0
- package/dist/recipes.js +340 -0
- package/dist/recipes.js.map +1 -0
- package/dist/server.js +19 -3
- package/dist/server.js.map +1 -1
- package/package.json +20 -6
- package/recipes/job-applicant/agent.md +142 -0
- package/recipes/job-applicant/learnings.md +15 -0
- package/recipes/job-applicant/recipe.yaml +87 -0
- package/recipes/qa-engineer/agent.md +197 -0
- package/recipes/qa-engineer/learnings.md +15 -0
- package/recipes/qa-engineer/recipe.yaml +95 -0
- package/recipes/web-researcher/agent.md +126 -0
- package/recipes/web-researcher/learnings.md +3 -0
- package/recipes/web-researcher/recipe.yaml +69 -0
package/dist/recipes.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe manager — list, read, create, import, export, and log memory
|
|
3
|
+
* for self-improving agent recipes. All file operations are local.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
// ── Paths ───────────────────────────────────────────────────
|
|
10
|
+
/** Bundled recipes shipped with the package */
|
|
11
|
+
function bundledRecipesDir() {
|
|
12
|
+
// recipes/ sits next to src/ in the package root
|
|
13
|
+
const srcDir = path.dirname(new URL(import.meta.url).pathname);
|
|
14
|
+
return path.join(srcDir, "..", "recipes");
|
|
15
|
+
}
|
|
16
|
+
/** User's local recipe store */
|
|
17
|
+
function userRecipesDir() {
|
|
18
|
+
const dir = path.join(os.homedir(), ".neuron", "recipes");
|
|
19
|
+
if (!fs.existsSync(dir))
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
/** User profile path */
|
|
24
|
+
function profilePath() {
|
|
25
|
+
return path.join(os.homedir(), ".neuron", "profile.yaml");
|
|
26
|
+
}
|
|
27
|
+
// ── Helpers ─────────────────────────────────────────────────
|
|
28
|
+
function readText(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
return fs.readFileSync(filePath, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function readYamlish(text) {
|
|
37
|
+
// Minimal YAML-like parser for recipe.yaml — handles flat and simple nested keys.
|
|
38
|
+
// Not a full YAML parser; good enough for structured recipe metadata.
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const line of text.split("\n")) {
|
|
41
|
+
const match = line.match(/^(\w[\w_-]*):\s*(.+)$/);
|
|
42
|
+
if (match) {
|
|
43
|
+
const val = match[2].trim();
|
|
44
|
+
if (val === "true")
|
|
45
|
+
result[match[1]] = true;
|
|
46
|
+
else if (val === "false")
|
|
47
|
+
result[match[1]] = false;
|
|
48
|
+
else if (/^\d+$/.test(val))
|
|
49
|
+
result[match[1]] = parseInt(val, 10);
|
|
50
|
+
else
|
|
51
|
+
result[match[1]] = val.replace(/^["']|["']$/g, "");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function parseRecipe(dir, location) {
|
|
57
|
+
const yamlPath = path.join(dir, "recipe.yaml");
|
|
58
|
+
const yamlText = readText(yamlPath);
|
|
59
|
+
if (!yamlText)
|
|
60
|
+
return null;
|
|
61
|
+
const meta = readYamlish(yamlText);
|
|
62
|
+
// Parse tags from YAML list format
|
|
63
|
+
const tagsMatch = yamlText.match(/^tags:\s*\[([^\]]*)\]/m);
|
|
64
|
+
const tags = tagsMatch
|
|
65
|
+
? tagsMatch[1].split(",").map((t) => t.trim().replace(/^["']|["']$/g, "")).filter(Boolean)
|
|
66
|
+
: [];
|
|
67
|
+
const memoryDir = path.join(dir, "memory");
|
|
68
|
+
const memoryFiles = fs.existsSync(memoryDir)
|
|
69
|
+
? fs.readdirSync(memoryDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))
|
|
70
|
+
: [];
|
|
71
|
+
const learningsPath = path.join(dir, "learnings.md");
|
|
72
|
+
return {
|
|
73
|
+
name: String(meta.name || path.basename(dir)),
|
|
74
|
+
slug: path.basename(dir),
|
|
75
|
+
version: String(meta.version || "0.0.0"),
|
|
76
|
+
description: String(meta.description || ""),
|
|
77
|
+
author: String(meta.author || "unknown"),
|
|
78
|
+
tags,
|
|
79
|
+
location,
|
|
80
|
+
path: dir,
|
|
81
|
+
has_learnings: fs.existsSync(learningsPath) && (readText(learningsPath) ?? "").length > 100,
|
|
82
|
+
has_memory: memoryFiles.length > 0,
|
|
83
|
+
memory_count: memoryFiles.length,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ── Public API (called by MCP tool handlers) ────────────────
|
|
87
|
+
export function listRecipes() {
|
|
88
|
+
const recipes = [];
|
|
89
|
+
// Bundled
|
|
90
|
+
const bundled = bundledRecipesDir();
|
|
91
|
+
if (fs.existsSync(bundled)) {
|
|
92
|
+
for (const entry of fs.readdirSync(bundled, { withFileTypes: true })) {
|
|
93
|
+
if (!entry.isDirectory())
|
|
94
|
+
continue;
|
|
95
|
+
const r = parseRecipe(path.join(bundled, entry.name), "bundled");
|
|
96
|
+
if (r)
|
|
97
|
+
recipes.push(r);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// User
|
|
101
|
+
const user = userRecipesDir();
|
|
102
|
+
for (const entry of fs.readdirSync(user, { withFileTypes: true })) {
|
|
103
|
+
if (!entry.isDirectory())
|
|
104
|
+
continue;
|
|
105
|
+
const r = parseRecipe(path.join(user, entry.name), "user");
|
|
106
|
+
if (r)
|
|
107
|
+
recipes.push(r);
|
|
108
|
+
}
|
|
109
|
+
return recipes;
|
|
110
|
+
}
|
|
111
|
+
export function getRecipe(slug) {
|
|
112
|
+
// Check user dir first (overrides bundled)
|
|
113
|
+
const userDir = path.join(userRecipesDir(), slug);
|
|
114
|
+
const bundledDir = path.join(bundledRecipesDir(), slug);
|
|
115
|
+
const dir = fs.existsSync(userDir) ? userDir : fs.existsSync(bundledDir) ? bundledDir : null;
|
|
116
|
+
if (!dir)
|
|
117
|
+
return null;
|
|
118
|
+
const location = dir === userDir ? "user" : "bundled";
|
|
119
|
+
const meta = parseRecipe(dir, location);
|
|
120
|
+
if (!meta)
|
|
121
|
+
return null;
|
|
122
|
+
const agentMd = readText(path.join(dir, "agent.md")) ?? "";
|
|
123
|
+
const recipeYaml = readText(path.join(dir, "recipe.yaml")) ?? "";
|
|
124
|
+
const learnings = readText(path.join(dir, "learnings.md"));
|
|
125
|
+
// Parse variables from recipe.yaml (simplified)
|
|
126
|
+
const variables = {};
|
|
127
|
+
const varSection = recipeYaml.match(/^variables:\n((?:[\s].*\n)*)/m);
|
|
128
|
+
if (varSection) {
|
|
129
|
+
const varLines = varSection[1].split("\n");
|
|
130
|
+
let currentVar = "";
|
|
131
|
+
for (const line of varLines) {
|
|
132
|
+
const nameMatch = line.match(/^\s{2}(\w[\w_-]*):/);
|
|
133
|
+
if (nameMatch) {
|
|
134
|
+
currentVar = nameMatch[1];
|
|
135
|
+
variables[currentVar] = {};
|
|
136
|
+
}
|
|
137
|
+
else if (currentVar) {
|
|
138
|
+
const propMatch = line.match(/^\s{4}(\w+):\s*(.+)/);
|
|
139
|
+
if (propMatch) {
|
|
140
|
+
variables[currentVar][propMatch[1]] = propMatch[2].trim().replace(/^["']|["']$/g, "");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { meta, agent_md: agentMd, recipe_yaml: recipeYaml, learnings, variables };
|
|
146
|
+
}
|
|
147
|
+
export function createRecipe(slug, name, description, agentMd, recipeYaml) {
|
|
148
|
+
const dir = path.join(userRecipesDir(), slug);
|
|
149
|
+
if (fs.existsSync(dir)) {
|
|
150
|
+
throw new Error(`Recipe "${slug}" already exists at ${dir}. Use a different slug or delete the existing one.`);
|
|
151
|
+
}
|
|
152
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
153
|
+
fs.writeFileSync(path.join(dir, "agent.md"), agentMd);
|
|
154
|
+
fs.writeFileSync(path.join(dir, "recipe.yaml"), recipeYaml);
|
|
155
|
+
fs.writeFileSync(path.join(dir, "learnings.md"), `# Learnings\n\nNo runs yet.\n`);
|
|
156
|
+
return { path: dir };
|
|
157
|
+
}
|
|
158
|
+
export function updateRecipe(slug, updates) {
|
|
159
|
+
const userDir = path.join(userRecipesDir(), slug);
|
|
160
|
+
const bundledDir = path.join(bundledRecipesDir(), slug);
|
|
161
|
+
let dir;
|
|
162
|
+
if (fs.existsSync(userDir)) {
|
|
163
|
+
dir = userDir;
|
|
164
|
+
}
|
|
165
|
+
else if (fs.existsSync(bundledDir)) {
|
|
166
|
+
// Fork bundled recipe to user dir before editing
|
|
167
|
+
fs.cpSync(bundledDir, userDir, { recursive: true });
|
|
168
|
+
dir = userDir;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
throw new Error(`Recipe "${slug}" not found.`);
|
|
172
|
+
}
|
|
173
|
+
if (updates.agent_md)
|
|
174
|
+
fs.writeFileSync(path.join(dir, "agent.md"), updates.agent_md);
|
|
175
|
+
if (updates.learnings)
|
|
176
|
+
fs.writeFileSync(path.join(dir, "learnings.md"), updates.learnings);
|
|
177
|
+
if (updates.recipe_yaml)
|
|
178
|
+
fs.writeFileSync(path.join(dir, "recipe.yaml"), updates.recipe_yaml);
|
|
179
|
+
return { path: dir };
|
|
180
|
+
}
|
|
181
|
+
export function logMemory(slug, entry) {
|
|
182
|
+
const userDir = path.join(userRecipesDir(), slug);
|
|
183
|
+
const bundledDir = path.join(bundledRecipesDir(), slug);
|
|
184
|
+
// Memory always goes to user dir
|
|
185
|
+
let dir;
|
|
186
|
+
if (fs.existsSync(userDir)) {
|
|
187
|
+
dir = userDir;
|
|
188
|
+
}
|
|
189
|
+
else if (fs.existsSync(bundledDir)) {
|
|
190
|
+
// Fork to user dir
|
|
191
|
+
fs.cpSync(bundledDir, userDir, { recursive: true });
|
|
192
|
+
dir = userDir;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
throw new Error(`Recipe "${slug}" not found.`);
|
|
196
|
+
}
|
|
197
|
+
const memDir = path.join(dir, "memory");
|
|
198
|
+
if (!fs.existsSync(memDir))
|
|
199
|
+
fs.mkdirSync(memDir, { recursive: true });
|
|
200
|
+
const now = new Date();
|
|
201
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
202
|
+
const count = fs.readdirSync(memDir).length + 1;
|
|
203
|
+
const filename = `${ts}-run-${String(count).padStart(3, "0")}.yaml`;
|
|
204
|
+
const filepath = path.join(memDir, filename);
|
|
205
|
+
// Simple YAML-like serialization
|
|
206
|
+
const lines = [`date: ${now.toISOString()}`];
|
|
207
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
208
|
+
if (key === "date")
|
|
209
|
+
continue;
|
|
210
|
+
if (typeof value === "object" && value !== null) {
|
|
211
|
+
lines.push(`${key}:`);
|
|
212
|
+
for (const [k2, v2] of Object.entries(value)) {
|
|
213
|
+
lines.push(` ${k2}: ${JSON.stringify(v2)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
fs.writeFileSync(filepath, lines.join("\n") + "\n");
|
|
221
|
+
return { path: filepath };
|
|
222
|
+
}
|
|
223
|
+
export function getMemory(slug, limit = 20) {
|
|
224
|
+
const userDir = path.join(userRecipesDir(), slug);
|
|
225
|
+
const bundledDir = path.join(bundledRecipesDir(), slug);
|
|
226
|
+
const dir = fs.existsSync(userDir) ? userDir : fs.existsSync(bundledDir) ? bundledDir : null;
|
|
227
|
+
if (!dir)
|
|
228
|
+
throw new Error(`Recipe "${slug}" not found.`);
|
|
229
|
+
const memDir = path.join(dir, "memory");
|
|
230
|
+
if (!fs.existsSync(memDir))
|
|
231
|
+
return { entries: [], total: 0 };
|
|
232
|
+
const files = fs.readdirSync(memDir)
|
|
233
|
+
.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))
|
|
234
|
+
.sort()
|
|
235
|
+
.reverse();
|
|
236
|
+
const entries = [];
|
|
237
|
+
for (const f of files.slice(0, limit)) {
|
|
238
|
+
const text = readText(path.join(memDir, f));
|
|
239
|
+
if (text) {
|
|
240
|
+
entries.push({ _file: f, ...readYamlish(text) });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { entries, total: files.length };
|
|
244
|
+
}
|
|
245
|
+
export function importRecipe(source) {
|
|
246
|
+
let srcDir;
|
|
247
|
+
if (source.startsWith("github:")) {
|
|
248
|
+
// github:user/repo or github:user/repo/path
|
|
249
|
+
const parts = source.slice(7).split("/");
|
|
250
|
+
const repo = `${parts[0]}/${parts[1]}`;
|
|
251
|
+
const subpath = parts.slice(2).join("/");
|
|
252
|
+
const tmpDir = path.join(os.tmpdir(), `neuron-recipe-${Date.now()}`);
|
|
253
|
+
try {
|
|
254
|
+
execSync(`git clone --depth 1 https://github.com/${repo}.git "${tmpDir}"`, { stdio: "pipe" });
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
throw new Error(`Failed to clone https://github.com/${repo}`);
|
|
258
|
+
}
|
|
259
|
+
srcDir = subpath ? path.join(tmpDir, subpath) : tmpDir;
|
|
260
|
+
// If the cloned repo IS a recipe (has recipe.yaml at root), use it directly
|
|
261
|
+
if (!fs.existsSync(path.join(srcDir, "recipe.yaml"))) {
|
|
262
|
+
// Check if it's a repo with recipes/ subdir
|
|
263
|
+
const recipesSubdir = path.join(srcDir, "recipes");
|
|
264
|
+
if (fs.existsSync(recipesSubdir)) {
|
|
265
|
+
// Import all recipes from the repo
|
|
266
|
+
const imported = [];
|
|
267
|
+
for (const entry of fs.readdirSync(recipesSubdir, { withFileTypes: true })) {
|
|
268
|
+
if (!entry.isDirectory())
|
|
269
|
+
continue;
|
|
270
|
+
if (!fs.existsSync(path.join(recipesSubdir, entry.name, "recipe.yaml")))
|
|
271
|
+
continue;
|
|
272
|
+
const destDir = path.join(userRecipesDir(), entry.name);
|
|
273
|
+
if (fs.existsSync(destDir))
|
|
274
|
+
continue; // skip existing
|
|
275
|
+
fs.cpSync(path.join(recipesSubdir, entry.name), destDir, { recursive: true });
|
|
276
|
+
imported.push(entry.name);
|
|
277
|
+
}
|
|
278
|
+
// Clean up
|
|
279
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
280
|
+
if (imported.length === 0)
|
|
281
|
+
throw new Error("No recipes found in repository");
|
|
282
|
+
return { slug: imported.join(", "), path: userRecipesDir() };
|
|
283
|
+
}
|
|
284
|
+
throw new Error("No recipe.yaml found in repository root or recipes/ subdirectory");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// Local path
|
|
289
|
+
srcDir = path.resolve(source);
|
|
290
|
+
}
|
|
291
|
+
if (!fs.existsSync(path.join(srcDir, "recipe.yaml"))) {
|
|
292
|
+
throw new Error(`No recipe.yaml found at ${srcDir}`);
|
|
293
|
+
}
|
|
294
|
+
const yamlText = readText(path.join(srcDir, "recipe.yaml"));
|
|
295
|
+
const meta = yamlText ? readYamlish(yamlText) : {};
|
|
296
|
+
const slug = String(meta.name || path.basename(srcDir)).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
297
|
+
const destDir = path.join(userRecipesDir(), slug);
|
|
298
|
+
if (fs.existsSync(destDir)) {
|
|
299
|
+
throw new Error(`Recipe "${slug}" already exists. Delete it first or use a different name.`);
|
|
300
|
+
}
|
|
301
|
+
fs.cpSync(srcDir, destDir, { recursive: true });
|
|
302
|
+
// Remove memory from imported recipe (local-only)
|
|
303
|
+
const importedMemory = path.join(destDir, "memory");
|
|
304
|
+
if (fs.existsSync(importedMemory)) {
|
|
305
|
+
fs.rmSync(importedMemory, { recursive: true, force: true });
|
|
306
|
+
}
|
|
307
|
+
return { slug, path: destDir };
|
|
308
|
+
}
|
|
309
|
+
export function exportRecipe(slug, includeLearnings = true) {
|
|
310
|
+
const recipe = getRecipe(slug);
|
|
311
|
+
if (!recipe)
|
|
312
|
+
throw new Error(`Recipe "${slug}" not found.`);
|
|
313
|
+
return {
|
|
314
|
+
agent_md: recipe.agent_md,
|
|
315
|
+
recipe_yaml: recipe.recipe_yaml,
|
|
316
|
+
learnings: includeLearnings ? recipe.learnings : null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
export function deleteRecipe(slug) {
|
|
320
|
+
const dir = path.join(userRecipesDir(), slug);
|
|
321
|
+
if (!fs.existsSync(dir)) {
|
|
322
|
+
throw new Error(`Recipe "${slug}" not found in user recipes. Bundled recipes can't be deleted.`);
|
|
323
|
+
}
|
|
324
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
325
|
+
}
|
|
326
|
+
export function getProfile() {
|
|
327
|
+
const text = readText(profilePath());
|
|
328
|
+
if (!text)
|
|
329
|
+
return null;
|
|
330
|
+
return readYamlish(text);
|
|
331
|
+
}
|
|
332
|
+
export function saveProfile(data) {
|
|
333
|
+
const dir = path.dirname(profilePath());
|
|
334
|
+
if (!fs.existsSync(dir))
|
|
335
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
336
|
+
const lines = Object.entries(data).map(([k, v]) => `${k}: "${v}"`);
|
|
337
|
+
fs.writeFileSync(profilePath(), lines.join("\n") + "\n");
|
|
338
|
+
return { path: profilePath() };
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=recipes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipes.js","sourceRoot":"","sources":["../src/recipes.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
3
3
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4
4
|
import { PendingCalls } from "./correlation.js";
|
|
5
5
|
import { TOOLS, toolToPrimitive } from "./tools.js";
|
|
6
|
+
import { RECIPE_TOOLS, handleRecipeTool } from "./recipe-tools.js";
|
|
6
7
|
const PORT = parseInt(process.env.NEURON_BRIDGE_PORT ?? "7377", 10);
|
|
7
8
|
// ── State ────────────────────────────────────────────────────
|
|
8
9
|
let extensionWs = null;
|
|
@@ -38,9 +39,9 @@ console.error(`[bridge] WebSocket server listening on 127.0.0.1:${PORT}`);
|
|
|
38
39
|
// ── MCP server (Claude Code / Cursor connects via stdio) ────
|
|
39
40
|
const mcp = new McpServer({
|
|
40
41
|
name: "neuron-inspector",
|
|
41
|
-
version: "0.
|
|
42
|
+
version: "0.2.0",
|
|
42
43
|
});
|
|
43
|
-
//
|
|
44
|
+
// Browser tools (forwarded to extension via WebSocket)
|
|
44
45
|
for (const tool of TOOLS) {
|
|
45
46
|
mcp.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
|
|
46
47
|
if (!extensionWs || extensionWs.readyState !== WebSocket.OPEN) {
|
|
@@ -64,11 +65,26 @@ for (const tool of TOOLS) {
|
|
|
64
65
|
}
|
|
65
66
|
});
|
|
66
67
|
}
|
|
68
|
+
// Recipe tools (local, no extension needed)
|
|
69
|
+
for (const tool of RECIPE_TOOLS) {
|
|
70
|
+
mcp.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await handleRecipeTool(tool.name, args);
|
|
73
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
67
83
|
// ── Start ────────────────────────────────────────────────────
|
|
68
84
|
async function main() {
|
|
69
85
|
const transport = new StdioServerTransport();
|
|
70
86
|
await mcp.connect(transport);
|
|
71
|
-
console.error(
|
|
87
|
+
console.error(`[bridge] MCP server ready on stdio (${TOOLS.length} browser + ${RECIPE_TOOLS.length} recipe tools)`);
|
|
72
88
|
}
|
|
73
89
|
main().catch((err) => {
|
|
74
90
|
console.error("[bridge] Fatal:", err);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuron-inspector",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "51 browser tools for AI agents. Turn Chrome into an MCP server — inspect DOM, automate clicks, audit security, mock APIs, extract data, record demos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
7
7
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/**",
|
|
12
|
-
"bin/**"
|
|
12
|
+
"bin/**",
|
|
13
|
+
"recipes/**"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"build": "tsc",
|
|
@@ -21,16 +22,29 @@
|
|
|
21
22
|
"homepage": "https://neuron.ng/extension",
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
24
|
-
"url": "https://github.com/conquext/neuron.git"
|
|
25
|
-
"directory": "packages/extension/bridge"
|
|
25
|
+
"url": "https://github.com/conquext/neuron-inspector.git"
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
28
28
|
"neuron",
|
|
29
29
|
"mcp",
|
|
30
|
+
"mcp-server",
|
|
31
|
+
"model-context-protocol",
|
|
30
32
|
"chrome-extension",
|
|
31
33
|
"browser-tools",
|
|
34
|
+
"browser-automation",
|
|
32
35
|
"inspector",
|
|
33
|
-
"devtools"
|
|
36
|
+
"devtools",
|
|
37
|
+
"ai-agents",
|
|
38
|
+
"claude-code",
|
|
39
|
+
"cursor",
|
|
40
|
+
"windsurf",
|
|
41
|
+
"dom-inspector",
|
|
42
|
+
"security-audit",
|
|
43
|
+
"accessibility",
|
|
44
|
+
"seo-audit",
|
|
45
|
+
"network-mocking",
|
|
46
|
+
"web-scraping",
|
|
47
|
+
"browser-devtools"
|
|
34
48
|
],
|
|
35
49
|
"dependencies": {
|
|
36
50
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Job Applicant
|
|
2
|
+
|
|
3
|
+
You are a job application agent. You search job boards, evaluate postings against the user's profile, write tailored cover letters, fill application forms, and track everything. You learn which approaches get responses and which don't.
|
|
4
|
+
|
|
5
|
+
## Strategy
|
|
6
|
+
|
|
7
|
+
### Phase 0: Load context
|
|
8
|
+
|
|
9
|
+
Before anything:
|
|
10
|
+
1. Read the resume at `{{resume_path}}` and extract: skills, experience years, past companies, education, notable projects
|
|
11
|
+
2. Read `learnings.md` for accumulated intelligence on what's working
|
|
12
|
+
3. If `{{input.company_research}}` exists, read it for company-specific context
|
|
13
|
+
4. Read `{{output_path}}/applications.yaml` to know what's already been applied to (never re-apply)
|
|
14
|
+
|
|
15
|
+
### Phase 1: Find postings
|
|
16
|
+
|
|
17
|
+
Search {{job_boards}} for `{{target_roles}}` in `{{locations}}`.
|
|
18
|
+
|
|
19
|
+
1. `neuron_navigate` to the job board
|
|
20
|
+
2. `neuron_find_elements` for the search box → `neuron_type` the role query
|
|
21
|
+
3. If location filter exists, set it
|
|
22
|
+
4. `neuron_extract_data` on the results — pull: title, company, location, posted date, URL
|
|
23
|
+
5. `neuron_scroll` to load more results if the page lazy-loads
|
|
24
|
+
|
|
25
|
+
Filter out:
|
|
26
|
+
- Already applied (check applications.yaml)
|
|
27
|
+
- Locations that don't match `{{locations}}`
|
|
28
|
+
- Any posting that contains `{{deal_breakers}}`
|
|
29
|
+
|
|
30
|
+
Score remaining postings 1-5 on fit based on: skills match, seniority match, company type, posted recency.
|
|
31
|
+
|
|
32
|
+
### Phase 2: Evaluate
|
|
33
|
+
|
|
34
|
+
For each posting scored 3+, open it and deep-read:
|
|
35
|
+
|
|
36
|
+
1. `neuron_navigate` to the posting URL
|
|
37
|
+
2. `neuron_extract_data` to pull the full job description
|
|
38
|
+
3. Score the match in detail:
|
|
39
|
+
- Skills match: what % of required skills does the resume cover?
|
|
40
|
+
- Seniority match: is the level right?
|
|
41
|
+
- Red flags: any of `{{deal_breakers}}`?
|
|
42
|
+
- Salary: if posted, does it meet `{{salary_min}}`?
|
|
43
|
+
|
|
44
|
+
If the match score is 3+ and there are no red flags, proceed to Phase 3.
|
|
45
|
+
|
|
46
|
+
### Phase 3: Write cover letter
|
|
47
|
+
|
|
48
|
+
For each qualified posting, generate a tailored cover letter:
|
|
49
|
+
|
|
50
|
+
1. **Opening:** Reference something specific about the company (from the job description or `{{input.company_research}}` if available). Never use "I'm excited to apply" — start with what you noticed about their work.
|
|
51
|
+
2. **Match:** Connect 2-3 specific resume items to their requirements. Use concrete examples, not adjectives.
|
|
52
|
+
3. **Value-add:** One thing you'd bring that they didn't explicitly ask for but would benefit from.
|
|
53
|
+
4. **Close:** Specific, short, no desperation.
|
|
54
|
+
|
|
55
|
+
Tone: `{{cover_letter_tone}}`
|
|
56
|
+
|
|
57
|
+
Save the cover letter to `{{output_path}}/covers/{{company_slug}}-{{date}}.md`.
|
|
58
|
+
|
|
59
|
+
Check `learnings.md` for what's worked before. If formal openings get more responses than casual ones, adjust. If mentioning specific projects gets callbacks, do more of that.
|
|
60
|
+
|
|
61
|
+
### Phase 4: Apply
|
|
62
|
+
|
|
63
|
+
Navigate to the application form:
|
|
64
|
+
|
|
65
|
+
1. `neuron_find_elements` to map the form fields
|
|
66
|
+
2. `neuron_type` to fill standard fields (name, email, phone, LinkedIn from profile)
|
|
67
|
+
3. For resume upload: `neuron_find_elements` for file input, flag for human if needed
|
|
68
|
+
4. For cover letter fields: paste the generated cover letter
|
|
69
|
+
5. For custom questions: answer based on resume context — be specific, not generic
|
|
70
|
+
|
|
71
|
+
Before submission:
|
|
72
|
+
1. `neuron_screenshot` the filled form
|
|
73
|
+
2. If `{{auto_submit}}` is "review": stop and ask the user to review
|
|
74
|
+
3. If "auto": `neuron_click` the submit button, then `neuron_screenshot` the confirmation
|
|
75
|
+
|
|
76
|
+
### Phase 5: Record
|
|
77
|
+
|
|
78
|
+
Log the application:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- date: {{now}}
|
|
82
|
+
company: "<company name>"
|
|
83
|
+
role: "<role title>"
|
|
84
|
+
url: "<posting URL>"
|
|
85
|
+
match_score: <1-5>
|
|
86
|
+
cover_letter: "<path to cover letter file>"
|
|
87
|
+
status: submitted
|
|
88
|
+
response: pending
|
|
89
|
+
notes: "<anything notable about this application>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Append to `{{output_path}}/applications.yaml`.
|
|
93
|
+
|
|
94
|
+
## Reflect
|
|
95
|
+
|
|
96
|
+
After each run, create a memory entry:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
date: {{now}}
|
|
100
|
+
outcome:
|
|
101
|
+
postings_found: <count>
|
|
102
|
+
postings_qualified: <count scoring 3+>
|
|
103
|
+
applications_submitted: <count>
|
|
104
|
+
cover_letter_approach: "<what style/angle was used>"
|
|
105
|
+
form_fill_issues:
|
|
106
|
+
- "<any form that was tricky and why>"
|
|
107
|
+
time_per_application_minutes: <average>
|
|
108
|
+
boards_searched:
|
|
109
|
+
- board: "<name>"
|
|
110
|
+
results_quality: <1-5>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Tracking responses (manual)
|
|
114
|
+
|
|
115
|
+
When a response comes back (interview invite, rejection, silence after 2 weeks), update the application entry's `status` and `response` fields. The evolve phase uses this data.
|
|
116
|
+
|
|
117
|
+
## Evolve
|
|
118
|
+
|
|
119
|
+
After 10+ applications with at least 3 response outcomes, review memory and update `learnings.md`:
|
|
120
|
+
|
|
121
|
+
**Cover letter strategy:**
|
|
122
|
+
- Which openings get responses? Compare formal vs conversational vs technical.
|
|
123
|
+
- Does mentioning specific projects/numbers help?
|
|
124
|
+
- Does company-specific research in the opening correlate with callbacks?
|
|
125
|
+
- What length works? Short (3 para) vs detailed (5+ para)?
|
|
126
|
+
|
|
127
|
+
**Board quality:**
|
|
128
|
+
- Which boards have the most relevant postings?
|
|
129
|
+
- Which boards have the freshest postings (not stale 30+ day old)?
|
|
130
|
+
- Which boards' application forms are easiest to fill programmatically?
|
|
131
|
+
|
|
132
|
+
**Match scoring:**
|
|
133
|
+
- Are the 3+ scored postings actually converting to interviews?
|
|
134
|
+
- Should the scoring weights change? (e.g., skills match matters more than seniority match)
|
|
135
|
+
|
|
136
|
+
**Form filling:**
|
|
137
|
+
- Are there common custom questions that should have pre-written answers?
|
|
138
|
+
- Which form patterns are consistently tricky?
|
|
139
|
+
|
|
140
|
+
Update learnings, then update Strategy. If the data shows that technical-tone cover letters with specific numbers get 3x the response rate, make that the default approach regardless of the `cover_letter_tone` variable.
|
|
141
|
+
|
|
142
|
+
The goal is not more applications. It's more *responses per application*.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Learnings
|
|
2
|
+
|
|
3
|
+
No applications submitted yet. This file updates after 10+ applications with response data.
|
|
4
|
+
|
|
5
|
+
## Cover Letter Defaults
|
|
6
|
+
|
|
7
|
+
Starting assumptions (to be validated):
|
|
8
|
+
- Lead with something specific about the company, never "I'm excited to apply"
|
|
9
|
+
- Mention 2-3 concrete examples from past work, not adjectives
|
|
10
|
+
- Keep to 3-4 paragraphs
|
|
11
|
+
- Close with a specific ask, not "I look forward to hearing from you"
|
|
12
|
+
|
|
13
|
+
## Board Notes
|
|
14
|
+
|
|
15
|
+
No data yet. Initial priority: LinkedIn Jobs, then Indeed, then direct Greenhouse/Lever links.
|