toolsProvider.js
"use strict";
/**
* Developer Growth Plugin β toolsProvider
*
* Tools:
* Skills Β· rate_skill, get_skill_map, assess_skill_gaps
* Learning Β· log_session, get_stats, list_sessions
* Goals Β· set_goal, update_goal, list_goals
* Resources Β· add_resource, list_resources, update_resource, search_resources
* Planning Β· generate_learning_path, generate_study_plan, weekly_review
* Content Β· explain_concept, generate_project_idea, search_ai_trends, compare_ai_tools
* Export Β· export_growth_report
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.toolsProvider = void 0;
const sdk_1 = require("@lmstudio/sdk");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const duckDuckScrape = __importStar(require("duck-duck-scrape"));
const zod_1 = require("zod");
const config_1 = require("./config");
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function json(obj) {
return JSON.stringify(obj, null, 2);
}
function safe_impl(name, fn) {
return async (params) => {
try {
return await fn(params);
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return JSON.stringify({
tool_error: true,
tool: name,
error: msg,
hint: "Read the error above, fix the parameter causing the issue, and retry the tool call.",
}, null, 2);
}
};
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function webSearch(query, max = 8) {
const r = await duckDuckScrape.search(query, { safeSearch: duckDuckScrape.SafeSearchType.OFF });
return (r.results ?? []).slice(0, max).map((h) => ({
title: h.title,
url: h.url,
snippet: h.description ?? "",
}));
}
// Practitioner blogs β best signal for applied AI/LLM content
const EXPERT_BLOGS = "site:eugeneyan.com OR site:lilianweng.github.io OR site:huyenchip.com OR site:simonwillison.net OR site:sebastianraschka.com";
// ---------------------------------------------------------------------------
// Skill categories
// ---------------------------------------------------------------------------
const SKILL_CATEGORIES = [
"prompt_engineering",
"rag_and_retrieval",
"agents_and_orchestration",
"llm_evaluation",
"ai_assisted_coding",
"llmops_production",
"system_design_ai",
"fine_tuning_and_peft",
"open_source_llms",
"ai_security_and_safety",
"vector_databases",
"multimodal_ai",
"cs_fundamentals",
"software_architecture",
"data_engineering",
"devops_and_infra",
"product_and_communication",
"other",
];
// Human-readable labels for categories
const CATEGORY_LABELS = {
prompt_engineering: "Prompt Engineering & LLM Interaction",
rag_and_retrieval: "RAG & Retrieval Systems",
agents_and_orchestration: "Agents, Tool Use & Orchestration",
llm_evaluation: "LLM Evaluation & Testing",
ai_assisted_coding: "AI-Assisted Coding",
llmops_production: "LLMOps & Production AI",
system_design_ai: "System Design for AI",
fine_tuning_and_peft: "Fine-tuning & PEFT",
open_source_llms: "Open-source LLMs & Local Models",
ai_security_and_safety: "AI Security & Safety",
vector_databases: "Vector Databases & Embeddings",
multimodal_ai: "Multimodal AI",
cs_fundamentals: "CS Fundamentals",
software_architecture: "Software Architecture",
data_engineering: "Data Engineering",
devops_and_infra: "DevOps & Infrastructure",
product_and_communication: "Product Thinking & Communication",
other: "Other",
};
// ---------------------------------------------------------------------------
// DB helpers
// ---------------------------------------------------------------------------
function getDataDir(configPath) {
return configPath.trim() || (0, path_1.join)((0, os_1.homedir)(), "developer-growth");
}
function dbPath(dataDir) {
return (0, path_1.join)(dataDir, "growth.json");
}
async function loadDB(dataDir) {
try {
const raw = await (0, promises_1.readFile)(dbPath(dataDir), "utf8");
const db = JSON.parse(raw);
db.skills ??= [];
db.sessions ??= [];
db.resources ??= [];
db.goals ??= [];
for (const r of db.resources) {
r.rating ??= 0;
r.tags ??= [];
}
for (const g of db.goals) {
g.milestones ??= [];
}
return db;
}
catch {
return { skills: [], sessions: [], resources: [], goals: [] };
}
}
async function saveDB(dataDir, db) {
await (0, promises_1.mkdir)(dataDir, { recursive: true });
await (0, promises_1.writeFile)(dbPath(dataDir), JSON.stringify(db, null, 2), "utf8");
}
function makeId() {
return crypto.randomUUID();
}
// ---------------------------------------------------------------------------
// Tools Provider
// ---------------------------------------------------------------------------
const toolsProvider = async (ctl) => {
const cfg = ctl.getPluginConfig(config_1.pluginConfigSchematics);
const dataDir = () => getDataDir(cfg.get("dataPath"));
const devName = () => cfg.get("developerName").trim() || "Developer";
const currentRole = () => cfg.get("currentRole").trim() || "Developer";
const targetRole = () => cfg.get("targetRole").trim();
const expYears = () => cfg.get("experienceYears");
const weeklyGoal = () => cfg.get("weeklyGoalMinutes");
const maxResults = () => cfg.get("maxSearchResults");
const tools = [
// =========================================================================
// SKILLS
// =========================================================================
(0, sdk_1.tool)({
name: "rate_skill",
description: (0, sdk_1.text) `
Self-rate your proficiency in a specific skill (1β10).
Saves to your skill map. Run get_skill_map to see your full picture.
Be honest β overrating hurts your learning plan.
Rating guide:
1β2 Never used it / heard the term only
3β4 Basic awareness, read about it
5β6 Used it in a project, understand core concepts
7β8 Comfortable, can use without much help
9β10 Deep expertise, could teach or build production systems
`,
parameters: {
skill: zod_1.z.string().describe("Specific skill to rate (e.g. 'RAG pipelines', 'Prompt chaining', 'LangChain')"),
category: zod_1.z.enum(SKILL_CATEGORIES).describe("Which LLM-era category this skill belongs to"),
rating: zod_1.z.coerce.number().int().min(1).max(10).describe("Your honest proficiency (1β10)"),
notes: zod_1.z.string().default("").describe("Context β what have you built, what do you still struggle with?"),
},
implementation: safe_impl("rate_skill", async ({ skill, category, rating, notes }) => {
const db = await loadDB(dataDir());
const existing = db.skills.findIndex((s) => s.skill.toLowerCase() === skill.toLowerCase());
const entry = {
skill,
category,
rating,
notes,
ratedAt: new Date().toISOString(),
};
if (existing !== -1) {
const prev = db.skills[existing].rating;
db.skills[existing] = entry;
await saveDB(dataDir(), db);
return json({
success: true,
skill,
category: CATEGORY_LABELS[category],
rating,
previousRating: prev,
change: rating - prev,
notes,
});
}
db.skills.push(entry);
await saveDB(dataDir(), db);
return json({ success: true, skill, category: CATEGORY_LABELS[category], rating, notes });
}),
}),
(0, sdk_1.tool)({
name: "get_skill_map",
description: (0, sdk_1.text) `
View your full skill map β all rated skills grouped by LLM-era category
with averages, coverage, and a radar chart-style summary.
Highlights strongest areas, weakest areas, and unrated priority categories
based on your current and target role.
`,
parameters: {
category: zod_1.z.enum([...SKILL_CATEGORIES, "all"]).default("all")
.describe("Filter to a specific category or 'all'"),
},
implementation: safe_impl("get_skill_map", async ({ category }) => {
const db = await loadDB(dataDir());
const role = currentRole();
const target = targetRole();
const year = new Date().getFullYear();
const roleContext = target ? `${role} transitioning to ${target}` : role;
let skills = db.skills;
if (category !== "all")
skills = skills.filter((s) => s.category === category);
// Group by category
const grouped = {};
for (const s of skills) {
if (!grouped[s.category])
grouped[s.category] = { skills: [], avg: 0 };
grouped[s.category].skills.push(s);
}
for (const cat of Object.keys(grouped)) {
const ratings = grouped[cat].skills.map((s) => s.rating);
grouped[cat].avg = Math.round((ratings.reduce((a, b) => a + b, 0) / ratings.length) * 10) / 10;
}
const strongest = db.skills.slice().sort((a, b) => b.rating - a.rating).slice(0, 3);
const weakest = db.skills.filter((s) => s.rating <= 4).sort((a, b) => a.rating - b.rating).slice(0, 3);
const overallAvg = db.skills.length > 0
? Math.round((db.skills.reduce((a, s) => a + s.rating, 0) / db.skills.length) * 10) / 10
: 0;
// Live search: what skills actually matter for this role right now
const roleSkillsSearch = await webSearch(`"${target || role}" skills most important ${year} what to know`, maxResults());
return json({
developer: devName(),
currentRole: role,
targetRole: target || "not set",
overallAverage: overallAvg,
totalSkillsRated: db.skills.length,
categoryBreakdown: Object.fromEntries(Object.entries(grouped).map(([cat, data]) => [
CATEGORY_LABELS[cat] ?? cat,
{ average: data.avg, skills: data.skills.map((s) => ({ skill: s.skill, rating: s.rating })) },
])),
strongest: strongest.map((s) => ({ skill: s.skill, rating: s.rating, category: CATEGORY_LABELS[s.category] })),
weakest: weakest.map((s) => ({ skill: s.skill, rating: s.rating, category: CATEGORY_LABELS[s.category] })),
role_skills_research: {
query: `"${target || role}" skills most important ${year}`,
results: roleSkillsSearch,
},
instructions: `For ${devName()} (${roleContext}): ` +
"Using the skill map AND the live role_skills_research above, identify: " +
"(1) The 2 most urgent unrated or low-rated skills given what the market says this role needs RIGHT NOW, " +
"(2) Which rated skills to double down on (they compound), " +
"(3) One area to deliberately ignore for now (focus tax is real), " +
"(4) A specific next skill to work on and exactly why β grounded in the search results.",
});
}),
}),
(0, sdk_1.tool)({
name: "assess_skill_gaps",
description: (0, sdk_1.text) `
Analyse your skill map against your current/target role and generate a
prioritised gap analysis. Identifies critical gaps (blocking your goal),
important gaps (high leverage), and nice-to-have gaps.
Also distinguishes between skills AI is replacing vs amplifying.
`,
parameters: {
focusArea: zod_1.z.string().default("")
.describe("Optional focus β e.g. 'getting to Staff Engineer', 'building an AI SaaS', 'switching to AI roles'"),
},
implementation: safe_impl("assess_skill_gaps", async ({ focusArea }) => {
const db = await loadDB(dataDir());
const role = currentRole();
const target = targetRole();
const years = expYears();
const year = new Date().getFullYear();
const targetRoleStr = (target || focusArea || "AI engineer").trim();
const transitionStr = target ? `${role} to ${target}` : focusArea || "developer to AI engineer";
// Live web searches β what does this role actually require RIGHT NOW
const searchQueries = [
{ angle: "role_requirements", query: `"${targetRoleStr}" skills required ${year} job description must-have` },
{ angle: "transition_path", query: `"${transitionStr}" transition how to ${year} roadmap` },
{ angle: "what_to_learn", query: `how to become "${targetRoleStr}" ${year} what to learn` },
{ angle: "hiring_bar", query: `"${targetRoleStr}" interview topics skills ${year} hiring` },
];
const webResearch = [];
for (const s of searchQueries) {
const results = await webSearch(s.query, maxResults());
webResearch.push({ angle: s.angle, query: s.query, results });
await sleep(350);
}
// Current self-rated skills from DB (context for the gap analysis)
const currentSkills = db.skills.map((s) => ({
skill: s.skill,
category: CATEGORY_LABELS[s.category],
rating: s.rating,
notes: s.notes,
ratedAt: s.ratedAt,
}));
return json({
developer: devName(),
currentRole: role,
targetRole: targetRoleStr,
experienceYears: years,
focusArea: focusArea || null,
totalSkillsRated: db.skills.length,
current_skills: currentSkills,
web_research: webResearch,
next_step: "IMPORTANT: Based on the web research above (not hardcoded lists), identify the 3 most critical " +
"skills this developer is missing for the target role. Then call generate_learning_path once per skill " +
"(3 calls total). Use the category enum value, set currentRating from current_skills (0 if not rated), " +
"targetRating=8, timelineWeeks=12, weeklyHours=5. After all 3 calls, write a complete transition roadmap.",
instructions: `Analyse the live web research above to understand what "${targetRoleStr}" actually requires in ${year}. ` +
`Compare against ${devName()}'s current_skills (${years} yrs experience as ${role}). ` +
"Produce a gap analysis showing: " +
"(1) The 3 most critical skill gaps based on what employers and the community say right now, " +
"(2) Which existing skills from their background transfer directly (mention these explicitly), " +
"(3) One skill they might be over-investing in because AI is replacing it, " +
"(4) One timeless skill worth deepening. " +
"Ground every claim in the search results β cite specific sources.",
});
}),
}),
// =========================================================================
// LEARNING TRACKER
// =========================================================================
(0, sdk_1.tool)({
name: "log_session",
description: (0, sdk_1.text) `
Log a learning session β what you studied, for how long, and what you learned.
Builds your streak, tracks weekly progress toward your goal, and surfaces patterns.
Minimum 10 minutes to count as a session.
`,
parameters: {
topic: zod_1.z.string().describe("What you studied (specific β e.g. 'LangGraph agent loops', not just 'AI')"),
category: zod_1.z.enum(SKILL_CATEGORIES).describe("Which skill category this belongs to"),
durationMinutes: zod_1.z.coerce.number().int().min(10).max(720)
.describe("How long you studied in minutes"),
insights: zod_1.z.string().default("").describe("Key takeaways β what clicked, what confused you, what you want to explore next"),
notes: zod_1.z.string().default("").describe("Free-form notes"),
resourcesUsed: zod_1.z.array(zod_1.z.string()).default([])
.describe("Resources you used β URLs, book names, course names"),
mood: zod_1.z.enum(["frustrated", "neutral", "productive", "excited"]).default("productive")
.describe("How did the session feel?"),
date: zod_1.z.string().default("").describe("Date (YYYY-MM-DD), leave blank for today"),
},
implementation: safe_impl("log_session", async (params) => {
const db = await loadDB(dataDir());
const today = new Date().toISOString().slice(0, 10);
const date = params.date.trim() || today;
const session = {
id: makeId(),
date,
topic: params.topic,
category: params.category,
durationMinutes: params.durationMinutes,
notes: params.notes,
insights: params.insights,
resourcesUsed: params.resourcesUsed,
mood: params.mood,
createdAt: new Date().toISOString(),
};
db.sessions.push(session);
await saveDB(dataDir(), db);
// Compute week stats (MonβSun)
const weekStart = new Date();
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1);
const weekStartStr = weekStart.toISOString().slice(0, 10);
const weekSessions = db.sessions.filter((s) => s.date >= weekStartStr);
const weekMinutes = weekSessions.reduce((a, s) => a + s.durationMinutes, 0);
const weekGoal = weeklyGoal();
// Compute streak (consecutive days with at least one session)
const sessionDates = new Set(db.sessions.map((s) => s.date));
let streak = 0;
const d = new Date();
while (sessionDates.has(d.toISOString().slice(0, 10))) {
streak++;
d.setDate(d.getDate() - 1);
}
return json({
success: true,
session: { id: session.id, topic: session.topic, durationMinutes: session.durationMinutes, date },
weekProgress: {
minutesThisWeek: weekMinutes,
weeklyGoalMinutes: weekGoal,
percentComplete: Math.round((weekMinutes / weekGoal) * 100),
remainingMinutes: Math.max(0, weekGoal - weekMinutes),
sessionsThisWeek: weekSessions.length,
},
streak: { days: streak, message: streak >= 7 ? "Week streak! Keep going." : streak >= 3 ? `${streak}-day streak` : streak === 1 ? "Day 1 β start of a streak" : "No current streak" },
totalSessionsEver: db.sessions.length,
totalHoursEver: Math.round(db.sessions.reduce((a, s) => a + s.durationMinutes, 0) / 60 * 10) / 10,
});
}),
}),
(0, sdk_1.tool)({
name: "get_stats",
description: (0, sdk_1.text) `
View your learning statistics: total hours, weekly progress, streak,
most-studied categories, mood patterns, and session history summary.
`,
parameters: {
period: zod_1.z.enum(["week", "month", "quarter", "all"]).default("month")
.describe("Time period to summarise"),
},
implementation: safe_impl("get_stats", async ({ period }) => {
const db = await loadDB(dataDir());
const today = new Date();
const todayStr = today.toISOString().slice(0, 10);
const cutoffDate = new Date(today);
if (period === "week")
cutoffDate.setDate(today.getDate() - 7);
else if (period === "month")
cutoffDate.setDate(today.getDate() - 30);
else if (period === "quarter")
cutoffDate.setDate(today.getDate() - 90);
const cutoff = period === "all" ? "0000-00-00" : cutoffDate.toISOString().slice(0, 10);
const sessions = db.sessions.filter((s) => s.date >= cutoff);
const totalMins = sessions.reduce((a, s) => a + s.durationMinutes, 0);
// Category breakdown
const catMins = {};
for (const s of sessions) {
catMins[s.category] = (catMins[s.category] ?? 0) + s.durationMinutes;
}
const topCategories = Object.entries(catMins)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([cat, mins]) => ({ category: CATEGORY_LABELS[cat] ?? cat, hours: Math.round(mins / 60 * 10) / 10 }));
// Mood breakdown
const moodCount = {};
for (const s of sessions)
moodCount[s.mood] = (moodCount[s.mood] ?? 0) + 1;
// Streak
const sessionDates = new Set(db.sessions.map((s) => s.date));
let streak = 0;
const d = new Date();
while (sessionDates.has(d.toISOString().slice(0, 10))) {
streak++;
d.setDate(d.getDate() - 1);
}
// Weekly goal progress (current week)
const weekStart = new Date();
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1);
const weekSessions = db.sessions.filter((s) => s.date >= weekStart.toISOString().slice(0, 10));
const weekMins = weekSessions.reduce((a, s) => a + s.durationMinutes, 0);
const weekGoal = weeklyGoal();
// Active goals
const activeGoals = db.goals.filter((g) => g.status === "active").length;
const overdueGoals = db.goals.filter((g) => g.status === "active" && g.targetDate < todayStr).length;
return json({
developer: devName(),
period,
sessions: sessions.length,
totalHours: Math.round(totalMins / 60 * 10) / 10,
avgSessionMinutes: sessions.length > 0 ? Math.round(totalMins / sessions.length) : 0,
currentStreak: { days: streak },
weeklyGoal: { current: weekMins, target: weekGoal, percent: Math.round((weekMins / weekGoal) * 100) },
topCategories,
moodBreakdown: moodCount,
allTime: {
totalSessions: db.sessions.length,
totalHours: Math.round(db.sessions.reduce((a, s) => a + s.durationMinutes, 0) / 60 * 10) / 10,
skillsRated: db.skills.length,
resourcesAdded: db.resources.length,
resourcesCompleted: db.resources.filter((r) => r.status === "completed").length,
},
goals: { active: activeGoals, overdue: overdueGoals },
});
}),
}),
(0, sdk_1.tool)({
name: "list_sessions",
description: (0, sdk_1.text) `
List recent learning sessions with insights. Useful for weekly review
or finding patterns in what you've been studying.
`,
parameters: {
limit: zod_1.z.coerce.number().int().min(1).max(50).default(10).describe("Number of sessions to return"),
category: zod_1.z.enum([...SKILL_CATEGORIES, "all"]).default("all"),
},
implementation: safe_impl("list_sessions", async ({ limit, category }) => {
const db = await loadDB(dataDir());
let sessions = db.sessions.slice().sort((a, b) => b.date.localeCompare(a.date));
if (category !== "all")
sessions = sessions.filter((s) => s.category === category);
sessions = sessions.slice(0, limit);
return json({
total: db.sessions.length,
shown: sessions.length,
sessions: sessions.map((s) => ({
id: s.id,
date: s.date,
topic: s.topic,
category: CATEGORY_LABELS[s.category],
durationMinutes: s.durationMinutes,
mood: s.mood,
insights: s.insights.slice(0, 150),
})),
});
}),
}),
// =========================================================================
// GOALS
// =========================================================================
(0, sdk_1.tool)({
name: "set_goal",
description: (0, sdk_1.text) `
Set a learning goal with a target date, category, and milestones.
Good goals are specific and time-bound: "Build a RAG pipeline from scratch by May 30"
not "Learn AI".
`,
parameters: {
title: zod_1.z.string().describe("Specific goal (e.g. 'Build a production RAG pipeline', 'Get comfortable with LangGraph')"),
description: zod_1.z.string().default("").describe("Why this goal matters to you and what done looks like"),
category: zod_1.z.enum(SKILL_CATEGORIES),
targetDate: zod_1.z.string().describe("Target completion date (YYYY-MM-DD)"),
milestones: zod_1.z.array(zod_1.z.string()).default([])
.describe("3β5 concrete milestones that mark progress toward this goal"),
},
implementation: safe_impl("set_goal", async (params) => {
const db = await loadDB(dataDir());
const goal = {
id: makeId(),
title: params.title,
description: params.description,
category: params.category,
targetDate: params.targetDate,
status: "active",
progressPercent: 0,
milestones: params.milestones,
notes: "",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
db.goals.push(goal);
await saveDB(dataDir(), db);
const today = new Date().toISOString().slice(0, 10);
const daysLeft = Math.floor((new Date(params.targetDate).getTime() - new Date(today).getTime()) / (1000 * 60 * 60 * 24));
return json({
success: true,
goal,
daysLeft,
weeklyHoursNeeded: daysLeft > 0 ? Math.round(((daysLeft / 7) * 2 * 10) / 10) : "past due",
});
}),
}),
(0, sdk_1.tool)({
name: "update_goal",
description: (0, sdk_1.text) `
Update a goal's progress, status, or notes. Call this regularly to keep
your goals current. Completing milestones also helps track momentum.
`,
parameters: {
id: zod_1.z.string().describe("Goal ID"),
progressPercent: zod_1.z.coerce.number().int().min(0).max(100).optional(),
status: zod_1.z.enum(["active", "completed", "abandoned"]).optional(),
notes: zod_1.z.string().optional(),
milestones: zod_1.z.array(zod_1.z.string()).optional().describe("Replace milestone list"),
},
implementation: safe_impl("update_goal", async ({ id, ...updates }) => {
const db = await loadDB(dataDir());
const idx = db.goals.findIndex((g) => g.id === id);
if (idx === -1)
throw new Error(`Goal ID '${id}' not found.`);
const patch = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
db.goals[idx] = { ...db.goals[idx], ...patch, updatedAt: new Date().toISOString() };
await saveDB(dataDir(), db);
return json({ success: true, goal: db.goals[idx] });
}),
}),
(0, sdk_1.tool)({
name: "list_goals",
description: "List all learning goals with progress and days remaining.",
parameters: {
status: zod_1.z.enum(["active", "completed", "abandoned", "all"]).default("active"),
},
implementation: safe_impl("list_goals", async ({ status }) => {
const db = await loadDB(dataDir());
const today = new Date().toISOString().slice(0, 10);
let goals = db.goals;
if (status !== "all")
goals = goals.filter((g) => g.status === status);
goals = goals.slice().sort((a, b) => a.targetDate.localeCompare(b.targetDate));
return json({
total: goals.length,
goals: goals.map((g) => {
const daysLeft = Math.floor((new Date(g.targetDate).getTime() - new Date(today).getTime()) / (1000 * 60 * 60 * 24));
return {
id: g.id,
title: g.title,
category: CATEGORY_LABELS[g.category],
status: g.status,
progressPercent: g.progressPercent,
targetDate: g.targetDate,
daysLeft,
overdue: daysLeft < 0 && g.status === "active",
milestones: g.milestones,
};
}),
});
}),
}),
// =========================================================================
// RESOURCES
// =========================================================================
(0, sdk_1.tool)({
name: "add_resource",
description: (0, sdk_1.text) `
Save a learning resource (paper, course, book, article, video, repo, podcast).
Build a personal reading/watching list tied to your skill goals.
`,
parameters: {
title: zod_1.z.string().describe("Resource title"),
url: zod_1.z.string().default("").describe("URL or reference"),
type: zod_1.z.enum(["paper", "course", "book", "article", "video", "repo", "podcast", "other"]),
category: zod_1.z.enum(SKILL_CATEGORIES),
tags: zod_1.z.array(zod_1.z.string()).default([]),
priority: zod_1.z.enum(["low", "medium", "high"]).default("medium"),
notes: zod_1.z.string().default("").describe("Why you're saving this, what you want to get from it"),
},
implementation: safe_impl("add_resource", async (params) => {
const db = await loadDB(dataDir());
const resource = {
id: makeId(),
title: params.title,
url: params.url,
type: params.type,
category: params.category,
tags: params.tags,
priority: params.priority,
status: "unread",
notes: params.notes,
rating: 0,
addedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
db.resources.push(resource);
await saveDB(dataDir(), db);
return json({ success: true, resource });
}),
}),
(0, sdk_1.tool)({
name: "list_resources",
description: "List saved resources filtered by status, category, type, or priority.",
parameters: {
status: zod_1.z.enum(["unread", "in_progress", "completed", "abandoned", "all"]).default("unread"),
category: zod_1.z.enum([...SKILL_CATEGORIES, "all"]).default("all"),
type: zod_1.z.enum(["paper", "course", "book", "article", "video", "repo", "podcast", "other", "all"]).default("all"),
priority: zod_1.z.enum(["low", "medium", "high", "all"]).default("all"),
},
implementation: safe_impl("list_resources", async ({ status, category, type, priority }) => {
const db = await loadDB(dataDir());
let resources = db.resources;
if (status !== "all")
resources = resources.filter((r) => r.status === status);
if (category !== "all")
resources = resources.filter((r) => r.category === category);
if (type !== "all")
resources = resources.filter((r) => r.type === type);
if (priority !== "all")
resources = resources.filter((r) => r.priority === priority);
const priorityOrder = { high: 3, medium: 2, low: 1 };
resources = resources.slice().sort((a, b) => (priorityOrder[b.priority] ?? 0) - (priorityOrder[a.priority] ?? 0));
const stats = {
total: db.resources.length,
unread: db.resources.filter((r) => r.status === "unread").length,
in_progress: db.resources.filter((r) => r.status === "in_progress").length,
completed: db.resources.filter((r) => r.status === "completed").length,
};
return json({
stats,
filter: { status, category, type, priority },
resources: resources.map((r) => ({
id: r.id,
title: r.title,
type: r.type,
category: CATEGORY_LABELS[r.category],
priority: r.priority,
status: r.status,
rating: r.rating || "not rated",
url: r.url,
notes: r.notes.slice(0, 100),
})),
});
}),
}),
(0, sdk_1.tool)({
name: "update_resource",
description: "Update a resource's status, rating, or notes after reading/watching it.",
parameters: {
id: zod_1.z.string().describe("Resource ID"),
status: zod_1.z.enum(["unread", "in_progress", "completed", "abandoned"]).optional(),
rating: zod_1.z.coerce.number().int().min(1).max(5).optional().describe("Rating 1β5 after completing"),
notes: zod_1.z.string().optional().describe("Notes or key takeaways after reading"),
priority: zod_1.z.enum(["low", "medium", "high"]).optional(),
},
implementation: safe_impl("update_resource", async ({ id, ...updates }) => {
const db = await loadDB(dataDir());
const idx = db.resources.findIndex((r) => r.id === id);
if (idx === -1)
throw new Error(`Resource ID '${id}' not found.`);
const patch = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
db.resources[idx] = { ...db.resources[idx], ...patch, updatedAt: new Date().toISOString() };
await saveDB(dataDir(), db);
return json({ success: true, resource: db.resources[idx] });
}),
}),
(0, sdk_1.tool)({
name: "search_resources",
description: (0, sdk_1.text) `
Search the web for learning resources on a specific LLM-era skill or topic.
Targets high-signal sources: Hugging Face, Arxiv, GitHub, official docs,
Andrej Karpathy, Simon Willison, Eugene Yan, Chip Huyen, and other trusted voices.
`,
parameters: {
topic: zod_1.z.string().describe("Topic to search for (e.g. 'RAG evaluation', 'LangGraph tutorial', 'LoRA fine-tuning')"),
resourceType: zod_1.z.enum(["paper", "course", "tutorial", "repo", "blog", "any"]).default("any"),
level: zod_1.z.enum(["beginner", "intermediate", "advanced", "any"]).default("any"),
},
implementation: safe_impl("search_resources", async ({ topic, resourceType, level }) => {
const year = new Date().getFullYear();
const levelTag = level !== "any" ? ` ${level}` : "";
const typeMap = {
paper: `site:arxiv.org OR site:huggingface.co/papers`,
course: `course OR tutorial`,
tutorial: `tutorial OR guide OR "step by step"`,
repo: `site:github.com`,
blog: `${EXPERT_BLOGS} OR blog`,
any: "",
};
const siteFilter = typeMap[resourceType] ?? "";
const q = `${topic}${levelTag} LLM ${year} ${siteFilter}`.trim();
const r = await duckDuckScrape.search(q, {
safeSearch: duckDuckScrape.SafeSearchType.OFF,
});
const results = (r.results ?? []).slice(0, maxResults()).map((h) => ({
title: h.title,
url: h.url,
snippet: h.description,
}));
return json({
topic,
resourceType,
level,
results,
trustedSources: [
"arxiv.org β preprints and papers",
"huggingface.co β models, datasets, courses",
"github.com β repos and implementations",
"fast.ai β practical deep learning",
`practitioner blogs searched: ${EXPERT_BLOGS}`,
],
});
}),
}),
// =========================================================================
// PLANNING
// =========================================================================
(0, sdk_1.tool)({
name: "generate_learning_path",
description: (0, sdk_1.text) `
Generate a personalised learning roadmap for ONE specific skill.
Call this once per skill. For a full career transition, call it 3 times β
once for each of the top critical gaps returned by assess_skill_gaps.
Returns a phased plan: Foundation β Core Skills β Build Projects β Advanced,
with specific resources, time estimates, and milestone checkpoints.
Calibrated to the developer's current rating, target rating, and weekly hours.
`,
parameters: {
skill: zod_1.z.string().describe("Skill or area to build a path for (e.g. 'RAG systems', 'LLM Evaluation', 'Agents')"),
category: zod_1.z.enum(SKILL_CATEGORIES),
currentRating: zod_1.z.coerce.number().int().min(0).max(10).default(0)
.describe("Your current level in this skill (0 = never touched it)"),
targetRating: zod_1.z.coerce.number().int().min(1).max(10).default(8)
.describe("Where you want to get to (8 = comfortable building production systems)"),
timelineWeeks: zod_1.z.coerce.number().int().min(2).max(52).default(12)
.describe("Weeks you want to reach target rating in"),
weeklyHours: zod_1.z.coerce.number().min(1).max(40).default(5)
.describe("Hours per week you can commit"),
},
implementation: safe_impl("generate_learning_path", async (params) => {
const role = currentRole();
const target = targetRole();
const years = expYears();
const year = new Date().getFullYear();
const db = await loadDB(dataDir());
const completedInCategory = db.resources
.filter((r) => r.category === params.category && r.status === "completed")
.map((r) => r.title);
const totalHours = params.timelineWeeks * params.weeklyHours;
const ratingGap = params.targetRating - params.currentRating;
const depthLabel = params.currentRating <= 3 ? "beginner" : params.currentRating <= 6 ? "intermediate" : "advanced";
// Live searches for current, real resources β no hardcoded lists
const searchQueries = [
{ angle: "roadmap", query: `"${params.skill}" learning roadmap path ${year}` },
{ angle: "tutorials", query: `"${params.skill}" tutorial guide ${depthLabel} ${year} site:github.com OR site:huggingface.co OR site:arxiv.org` },
{ angle: "courses", query: `"${params.skill}" course ${year} best free` },
{ angle: "projects", query: `"${params.skill}" project ideas build practice hands-on ${year}` },
{ angle: "expert_blog", query: `"${params.skill}" ${year} ${EXPERT_BLOGS}` },
];
const webResources = [];
for (const s of searchQueries) {
const results = await webSearch(s.query, maxResults());
webResources.push({ angle: s.angle, query: s.query, results });
await sleep(350);
}
return json({
skill: params.skill,
category: CATEGORY_LABELS[params.category],
developer: { name: devName(), role, targetRole: target, years },
currentRating: params.currentRating,
targetRating: params.targetRating,
ratingGap,
currentLevel: depthLabel,
timelineWeeks: params.timelineWeeks,
weeklyHours: params.weeklyHours,
totalHours,
alreadyCompleted: completedInCategory.length > 0 ? completedInCategory : null,
web_resources: webResources,
instructions: `Using ONLY the live web_resources found above (not your training data), build a ${params.timelineWeeks}-week ` +
`learning path for ${devName()} to go from ${params.currentRating}/10 to ${params.targetRating}/10 in "${params.skill}". ` +
`Background: ${role}, ${years} yrs experience. Available: ${params.weeklyHours} hrs/week = ${totalHours} total hours. ` +
(completedInCategory.length > 0 ? `Already completed: ${completedInCategory.join(", ")} β skip these. ` : "") +
"Structure:\n" +
"**Phase 1 β Foundation** (weeks 1β2): Core concepts. Pick the single best beginner resource from web_resources.\n" +
"**Phase 2 β Core Skills** (weeks 3β6): Hands-on. Pick 1β2 tutorials/repos from web_resources.\n" +
"**Phase 3 β Build** (weeks 7β10): One concrete project idea. Find a real repo or example from web_resources to reference.\n" +
"**Phase 4 β Advanced** (weeks 11+): Papers, production patterns. Use the expert_blog results.\n" +
"For each resource: use the real URL from search results. Include estimated hours and a milestone. " +
"Be opinionated β pick the best, not all of them.",
});
}),
}),
(0, sdk_1.tool)({
name: "generate_study_plan",
description: (0, sdk_1.text) `
Plan a focused study session for today or the week.
Considers your active goals, unread high-priority resources, and recent session patterns.
Returns a concrete study agenda β not vague advice.
`,
parameters: {
sessionType: zod_1.z.enum(["today", "week"]).default("today")
.describe("Plan for a single session today or the full week"),
availableMinutes: zod_1.z.coerce.number().int().min(20).max(480).default(60)
.describe("How much time you have available (for 'today')"),
},
implementation: safe_impl("generate_study_plan", async ({ sessionType, availableMinutes }) => {
const db = await loadDB(dataDir());
const today = new Date().toISOString().slice(0, 10);
const activeGoals = db.goals.filter((g) => g.status === "active")
.sort((a, b) => a.targetDate.localeCompare(b.targetDate))
.slice(0, 3);
const highPriorityResources = db.resources
.filter((r) => r.status === "unread" && r.priority === "high")
.slice(0, 5)
.map((r) => ({ title: r.title, type: r.type, category: CATEGORY_LABELS[r.category] }));
const recentCategories = db.sessions
.filter((s) => s.date >= new Date(Date.now() - 7 * 864e5).toISOString().slice(0, 10))
.map((s) => s.category);
const recentCategorySet = [...new Set(recentCategories)];
// Week stats
const weekStart = new Date();
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1);
const weekMins = db.sessions
.filter((s) => s.date >= weekStart.toISOString().slice(0, 10))
.reduce((a, s) => a + s.durationMinutes, 0);
const remainingWeekMins = Math.max(0, weeklyGoal() - weekMins);
return json({
developer: devName(),
sessionType,
today,
availableMinutes: sessionType === "today" ? availableMinutes : null,
weeklyGoalMinutes: weeklyGoal(),
weekMinutesSoFar: weekMins,
remainingWeekMinutes: remainingWeekMins,
activeGoals: activeGoals.map((g) => ({
title: g.title,
category: CATEGORY_LABELS[g.category],
progress: g.progressPercent,
targetDate: g.targetDate,
daysLeft: Math.floor((new Date(g.targetDate).getTime() - Date.now()) / 864e5),
})),
highPriorityResources,
recentlyStudiedCategories: recentCategorySet.map((c) => CATEGORY_LABELS[c] ?? c),
instructions: sessionType === "today"
? `Plan a focused ${availableMinutes}-minute study session for ${devName()} today (${today}). ` +
`Remaining weekly goal: ${remainingWeekMins} mins. Active goals and top resources above. ` +
"Return: (1) What to study and why (pick ONE focus), (2) Exact agenda with time blocks, " +
"(3) A specific deliverable for the session (e.g. 'implement a basic RAG loop'), " +
"(4) One question to answer by the end of the session."
: `Plan the week for ${devName()}. Weekly goal: ${weeklyGoal()} mins, done so far: ${weekMins} mins. ` +
"Distribute learning across 4β5 sessions. " +
"Return a day-by-day plan (MonβFri) with topic, duration, and goal for each session.",
});
}),
}),
(0, sdk_1.tool)({
name: "weekly_review",
description: (0, sdk_1.text) `
Generate a structured weekly review β what you learned, how it connects
to your goals, what to adjust, and what to focus on next week.
Best done every Friday or Sunday. Takes ~15 minutes.
`,
parameters: {
additionalNotes: zod_1.z.string().default("")
.describe("Anything specific you want to reflect on this week"),
},
implementation: safe_impl("weekly_review", async ({ additionalNotes }) => {
const db = await loadDB(dataDir());
const today = new Date();
const todayStr = today.toISOString().slice(0, 10);
const weekStart = new Date(today);
weekStart.setDate(today.getDate() - 6);
const weekStartStr = weekStart.toISOString().slice(0, 10);
const weekSessions = db.sessions.filter((s) => s.date >= weekStartStr);
const weekMins = weekSessions.reduce((a, s) => a + s.durationMinutes, 0);
const weekGoal = weeklyGoal();
const goalMet = weekMins >= weekGoal;
const insights = weekSessions
.filter((s) => s.insights)
.map((s) => ({ topic: s.topic, insight: s.insights.slice(0, 200) }));
const completedResources = db.resources
.filter((r) => r.status === "completed" && r.updatedAt >= weekStartStr + "T00:00:00.000Z")
.map((r) => ({ title: r.title, type: r.type, rating: r.rating }));
const activeGoals = db.goals
.filter((g) => g.status === "active")
.map((g) => ({
title: g.title,
progress: g.progressPercent,
daysLeft: Math.floor((new Date(g.targetDate).getTime() - Date.now()) / 864e5),
overdue: g.targetDate < todayStr,
}));
// Streak
const sessionDates = new Set(db.sessions.map((s) => s.date));
let streak = 0;
const d = new Date();
while (sessionDates.has(d.toISOString().slice(0, 10))) {
streak++;
d.setDate(d.getDate() - 1);
}
return json({
developer: devName(),
weekOf: weekStartStr,
weeklyGoalMet: goalMet,
minutesThisWeek: weekMins,
weeklyGoalMinutes: weekGoal,
percentOfGoal: Math.round((weekMins / weekGoal) * 100),
sessionCount: weekSessions.length,
currentStreak: streak,
topicsStudied: weekSessions.map((s) => s.topic),
keyInsights: insights,
completedResources,
activeGoals,
additionalNotes,
instructions: `Run a weekly review for ${devName()} (${currentRole()}). ` +
`This week: ${weekSessions.length} sessions, ${Math.round(weekMins / 60 * 10) / 10} hours, ` +
`goal ${goalMet ? "MET" : "NOT MET"} (${weekMins}/${weekGoal} min). ` +
"Structure the review:\n" +
"**What I learned** β summarise the week's sessions and insights in 3 bullets\n" +
"**What clicked** β one concept that became clearer this week\n" +
"**What I'm stuck on** β one thing still confusing or hard\n" +
"**Goal progress** β honest assessment of each active goal\n" +
"**What to adjust** β one change to make next week (pace, focus, resources)\n" +
"**Next week focus** β one primary topic and one specific deliverable\n" +
(additionalNotes ? `Additional context: ${additionalNotes}` : ""),
});
}),
}),
// =========================================================================
// CONTENT & RESEARCH
// =========================================================================
(0, sdk_1.tool)({
name: "explain_concept",
description: (0, sdk_1.text) `
Get a clear explanation of any LLM-era concept at a specified depth.
Returns: what it is, why it matters, how it works, common misconceptions,
where it fits in the ecosystem, and what to build to understand it.
`,
parameters: {
concept: zod_1.z.string().describe("Concept to explain (e.g. 'RLHF', 'KV cache', 'speculative decoding', 'RAG vs fine-tuning', 'PEFT vs LoRA')"),
depth: zod_1.z.enum(["overview", "intermediate", "deep_dive"]).default("intermediate")
.describe("How deep to go"),
relatedTo: zod_1.z.string().default("").describe("Optional: relate the explanation to a specific use case or problem"),
},
implementation: safe_impl("explain_concept", async ({ concept, depth, relatedTo }) => {
const years = expYears();
const role = currentRole();
const year = new Date().getFullYear();
const db = await loadDB(dataDir());
const relevantSkills = db.skills.slice(0, 5).map((s) => `${s.skill} (${s.rating}/10)`);
// Live searches: current understanding + best resource right now
const searches = [
{ angle: "current_state", query: `"${concept}" explained ${year} how it works` },
{ angle: "best_resource", query: `"${concept}" best tutorial blog paper ${year} site:arxiv.org OR ${EXPERT_BLOGS} OR site:github.com` },
...(relatedTo ? [{ angle: "applied", query: `"${concept}" "${relatedTo}" practical example ${year}` }] : []),
];
const webResults = [];
for (const s of searches) {
webResults.push({ angle: s.angle, results: await webSearch(s.query, 6) });
await sleep(300);
}
return json({
concept,
depth,
relatedTo: relatedTo || null,
developerContext: { role, years, relevantSkills },
web_results: webResults,
instructions: `Explain "${concept}" at ${depth} depth for a ${years}-year ${role}. ` +
(relatedTo ? `Frame it in the context of: "${relatedTo}". ` : "") +
"Use the web_results above to ground your explanation in current understanding and cite a real resource. " +
"Structure:\n" +
"**What it is** (1β2 sentences, no jargon)\n" +
"**Why it matters** (practical impact β when does this change what you build?)\n" +
"**How it works** " + (depth === "overview" ? "(high-level mental model only)" : depth === "intermediate" ? "(key mechanism with a simple example)" : "(detailed mechanism, math if relevant, edge cases)") + "\n" +
"**Common misconceptions** (what people usually get wrong)\n" +
"**Where it fits** (what problem it solves, alternatives, when NOT to use it)\n" +
"**Build to understand** (one specific thing to build to make this concrete)\n" +
"**Best resource** β pick the single best link from web_results.best_resource with its real URL\n" +
"No padding. Be direct.",
});
}),
}),
(0, sdk_1.tool)({
name: "generate_project_idea",
description: (0, sdk_1.text) `
Generate a concrete, buildable project idea that develops a specific LLM-era skill.
Projects are the fastest way to go from understanding β competence.
Returns a scoped project with clear learning objectives, tech stack, and milestones.
Calibrated to your experience level and the skill you're building.
`,
parameters: {
skill: zod_1.z.string().describe("Skill you want to build (e.g. 'RAG', 'agents', 'LLM evaluation', 'fine-tuning')"),
category: zod_1.z.enum(SKILL_CATEGORIES),
difficulty: zod_1.z.enum(["weekend", "week", "month"]).default("week")
.describe("Scope: weekend (4β8hrs), week (10β20hrs), month (40β80hrs)"),
domain: zod_1.z.string().default("").describe("Optional: domain to apply the skill to (e.g. 'finance', 'code', 'healthcare', 'developer tools')"),
count: zod_1.z.coerce.number().int().min(1).max(5).default(3)
.describe("Number of project ideas to generate"),
},
implementation: safe_impl("generate_project_idea", async (params) => {
const role = currentRole();
const years = expYears();
const year = new Date().getFullYear();
const db = await loadDB(dataDir());
const completedInCategory = db.resources
.filter((r) => r.category === params.category && r.status === "completed")
.map((r) => r.title);
const domainStr = params.domain ? ` ${params.domain}` : "";
// Live searches: what are people actually building with this skill right now
const searches = [
{ angle: "project_ideas", query: `"${params.skill}"${domainStr} project ideas build ${year} github` },
{ angle: "real_examples", query: `"${params.skill}"${domainStr} example implementation repo ${year} site:github.com` },
{ angle: "tech_stack", query: `"${params.skill}" tech stack tools libraries ${year} best` },
{ angle: "portfolio", query: `"${params.skill}" portfolio project showcase ${year} developer` },
];
const webResults = [];
for (const s of searches) {
webResults.push({ angle: s.angle, results: await webSearch(s.query, 6) });
await sleep(300);
}
return json({
skill: params.skill,
category: CATEGORY_LABELS[params.category],
difficulty: params.difficulty,
domain: params.domain || "any",
developer: { role, years },
alreadyCompleted: completedInCategory.length > 0 ? completedInCategory : null,
web_results: webResults,
instructions: `Using the live web_results above, generate ${params.count} ${params.difficulty}-scope project idea(s) ` +
`that build "${params.skill}" skills for a ${years}-year ${role}. ` +
(params.domain ? `Domain focus: ${params.domain}. ` : "") +
"Base the tech stack on what real_examples and tech_stack searches show people using RIGHT NOW. " +
"Reference real repos from the web_results where relevant. " +
"For each project:\n" +
"**Project name** β specific, not generic\n" +
"**Problem it solves** β real problem, not toy\n" +
"**What you'll learn** β 3 concrete skills this builds\n" +
"**Tech stack** β exact current libraries/APIs from the search results\n" +
"**Reference repo** β a real GitHub link from web_results to use as inspiration (if found)\n" +
"**Milestones** β 3β5 steps from zero to done\n" +
"**Portfolio value** β how to present this\n" +
"Make the projects genuinely useful β something real, not a tutorial rehash.",
});
}),
}),
(0, sdk_1.tool)({
name: "search_ai_trends",
description: (0, sdk_1.text) `
Search for what's happening RIGHT NOW in the LLM and AI space.
Find recent papers, model releases, framework updates, and emerging patterns.
Filters for high-signal sources (Hugging Face, Arxiv, AI Twitter, research blogs).
Helps you stay current without drowning in noise.
`,
parameters: {
topic: zod_1.z.string().default("").describe("Specific area to track (blank = general AI/LLM trends)"),
focus: zod_1.z.enum(["papers", "releases", "tools", "opinions", "all"]).default("all")
.describe("What type of content to look for"),
},
implementation: safe_impl("search_ai_trends", async ({ topic, focus }) => {
const year = new Date().getFullYear();
const month = new Date().toLocaleString("default", { month: "long" });
const queries = {
papers: `${topic || "LLM"} research paper ${month} ${year} site:arxiv.org OR site:huggingface.co`,
releases: `${topic || "AI LLM"} new release model update ${month} ${year}`,
tools: `${topic || "AI developer"} tool framework library ${year} github`,
opinions: `${topic || "LLM AI"} ${year} ${month} ${EXPERT_BLOGS}`,
all: `${topic || "LLM AI"} ${month} ${year} latest`,
};
const q = queries[focus] ?? queries.all;
const r = await duckDuckScrape.search(q, {
safeSearch: duckDuckScrape.SafeSearchType.OFF,
});
const results = (r.results ?? []).slice(0, maxResults()).map((h) => ({
title: h.title,
url: h.url,
snippet: h.description,
}));
return json({
topic: topic || "AI/LLM general",
focus,
period: `${month} ${year}`,
results,
signalSources: {
papers: "arxiv.org, huggingface.co/papers",
news: "the-decoder.com, techcrunch.com/ai",
practitioners: EXPERT_BLOGS,
community: "reddit.com/r/LocalLLaMA, news.ycombinator.com",
},
instructions: "From these search results, identify: " +
"(1) The 2 most important developments a developer should know about, " +
"(2) One thing that's being overhyped vs genuinely useful, " +
"(3) One skill these trends suggest will be valuable in 6β12 months.",
});
}),
}),
(0, sdk_1.tool)({
name: "compare_ai_tools",
description: (0, sdk_1.text) `
Compare AI frameworks, libraries, or tools for a specific use case.
Covers: LangChain vs LlamaIndex vs raw API, vector DB options, orchestration frameworks,
embedding models, evaluation libraries, and more.
Returns trade-offs, when to use each, and a recommendation for your situation.
`,
parameters: {
useCase: zod_1.z.string().describe("What you're trying to build (e.g. 'document Q&A', 'multi-step agent', 'semantic search')"),
tools: zod_1.z.array(zod_1.z.string()).default([])
.describe("Specific tools to compare (blank = let the model suggest the main options)"),
constraints: zod_1.z.string().default("")
.describe("Your constraints: 'local-only', 'no API costs', 'production-ready', 'simplest possible', etc."),
},
implementation: safe_impl("compare_ai_tools", async ({ useCase, tools, constraints }) => {
const role = currentRole();
const years = expYears();
// Search for recent comparisons
const searchQuery = `${useCase} ${tools.join(" vs ")} ${new Date().getFullYear()} comparison best`;
const r = await duckDuckScrape.search(searchQuery, {
safeSearch: duckDuckScrape.SafeSearchType.OFF,
});
const searchResults = (r.results ?? []).slice(0, 5).map((h) => ({
title: h.title, url: h.url, snippet: h.description,
}));
return json({
useCase,
toolsToCompare: tools.length > 0 ? tools : "suggest the main options",
constraints: constraints || "none specified",
developer: { role, years },
searchResults,
instructions: `Compare tools for: "${useCase}"${tools.length > 0 ? ` (${tools.join(" vs ")})` : ""}. ` +
`Constraints: ${constraints || "none"}. Developer: ${years}-year ${role}. ` +
"Return:\n" +
"**Options** β list the main tools for this use case (if not specified)\n" +
"**Comparison table** β tradeoffs across: ease of use, production-readiness, performance, cost, community\n" +
"**When to use each** β one-line decision rule\n" +
"**My recommendation** β one specific pick for this use case and these constraints, with rationale\n" +
"**Watch out for** β common mistake when choosing tools in this space\n" +
"Be opinionated. 'It depends' is not helpful β make a call.",
});
}),
}),
// =========================================================================
// EXPORT
// =========================================================================
(0, sdk_1.tool)({
name: "export_growth_report",
description: (0, sdk_1.text) `
Export a full Markdown growth report to disk.
Includes: skill map, learning stats, goal progress, completed resources,
and a summary of recent insights. Good for quarterly reviews or sharing with a mentor.
`,
parameters: {
outputPath: zod_1.z.string().default("").describe("File path to save the report. Defaults to <dataPath>/report-<date>.md"),
period: zod_1.z.enum(["month", "quarter", "all"]).default("quarter"),
},
implementation: safe_impl("export_growth_report", async ({ outputPath, period }) => {
const db = await loadDB(dataDir());
const date = new Date().toISOString().slice(0, 10);
const outPath = outputPath.trim() || (0, path_1.join)(dataDir(), `growth-report-${date}.md`);
const cutoffDays = period === "month" ? 30 : period === "quarter" ? 90 : 36500;
const cutoff = new Date(Date.now() - cutoffDays * 864e5).toISOString().slice(0, 10);
const sessions = db.sessions.filter((s) => s.date >= cutoff);
const totalMins = sessions.reduce((a, s) => a + s.durationMinutes, 0);
let md = `# Developer Growth Report β ${devName()}\n`;
md += `**Generated**: ${date} | **Period**: ${period} | **Role**: ${currentRole()}\n\n`;
if (targetRole())
md += `**Target**: ${targetRole()}\n\n`;
md += `---\n\n`;
// Stats
md += `## Learning Stats (${period})\n\n`;
md += `| Metric | Value |\n|--------|-------|\n`;
md += `| Sessions | ${sessions.length} |\n`;
md += `| Total Hours | ${Math.round(totalMins / 60 * 10) / 10} |\n`;
md += `| Avg Session | ${sessions.length > 0 ? Math.round(totalMins / sessions.length) : 0} min |\n`;
md += `| Skills Rated | ${db.skills.length} |\n`;
md += `| Resources Completed | ${db.resources.filter((r) => r.status === "completed").length} |\n`;
md += `\n`;
// Skill map
if (db.skills.length > 0) {
md += `## Skill Map\n\n`;
const grouped = {};
for (const s of db.skills) {
if (!grouped[s.category])
grouped[s.category] = [];
grouped[s.category].push(s);
}
for (const [cat, skills] of Object.entries(grouped)) {
const avg = Math.round(skills.reduce((a, s) => a + s.rating, 0) / skills.length * 10) / 10;
md += `### ${CATEGORY_LABELS[cat] ?? cat} (avg ${avg}/10)\n\n`;
for (const s of skills.sort((a, b) => b.rating - a.rating)) {
const bar = "β".repeat(Math.round(s.rating)) + "β".repeat(10 - Math.round(s.rating));
md += `- **${s.skill}**: ${bar} ${s.rating}/10\n`;
}
md += `\n`;
}
}
// Goals
if (db.goals.length > 0) {
md += `## Goals\n\n`;
for (const g of db.goals) {
const icon = g.status === "completed" ? "β" : g.status === "abandoned" ? "β" : "β";
md += `${icon} **${g.title}** β ${g.progressPercent}% β due ${g.targetDate}\n`;
if (g.milestones.length > 0) {
for (const m of g.milestones)
md += ` - ${m}\n`;
}
md += `\n`;
}
}
// Recent insights
const insights = sessions.filter((s) => s.insights).slice(-10);
if (insights.length > 0) {
md += `## Key Insights (${period})\n\n`;
for (const s of insights) {
md += `**${s.date} β ${s.topic}**\n> ${s.insights}\n\n`;
}
}
// Completed resources
const completed = db.resources.filter((r) => r.status === "completed" && r.updatedAt >= cutoff + "T00:00:00.000Z");
if (completed.length > 0) {
md += `## Completed Resources\n\n`;
for (const r of completed) {
const stars = r.rating > 0 ? "β
".repeat(r.rating) + "β".repeat(5 - r.rating) : "not rated";
md += `- **${r.title}** (${r.type}) ${stars}\n`;
if (r.notes)
md += ` > ${r.notes.slice(0, 150)}\n`;
}
md += `\n`;
}
await (0, promises_1.mkdir)(dataDir(), { recursive: true });
await (0, promises_1.writeFile)(outPath, md, "utf8");
return json({
success: true,
path: outPath,
sessionsIncluded: sessions.length,
skillsIncluded: db.skills.length,
goalsIncluded: db.goals.length,
resourcesIncluded: completed.length,
});
}),
}),
];
return tools;
};
exports.toolsProvider = toolsProvider;