@deepagents/toolbox 0.18.0 → 0.20.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/index.js CHANGED
@@ -173,264 +173,196 @@ var duckStocks = tool3({
173
173
  }
174
174
  });
175
175
 
176
- // packages/toolbox/src/lib/scratchbad.ts
177
- import { tool as tool4 } from "ai";
176
+ // packages/toolbox/src/lib/filesystem.ts
177
+ import { dynamicTool, jsonSchema, tool as tool4 } from "ai";
178
+ import FastGlob from "fast-glob";
179
+ import spawn2 from "nano-spawn";
180
+ import { readFile, readdir, stat } from "node:fs/promises";
181
+ import { basename, join, relative } from "node:path";
178
182
  import z4 from "zod";
179
- import { toState } from "@deepagents/agent";
180
- var scratchpad_tool = tool4({
181
- description: `Tool for strategic reflection on research progress and decision-making.
183
+ import { fastembed, nodeSQLite, similaritySearch } from "@deepagents/retrieval";
184
+ import * as connectors from "@deepagents/retrieval/connectors";
185
+ import { ignorePatterns } from "@deepagents/retrieval/connectors";
182
186
 
183
- Use this tool after each search to analyze results and plan next steps systematically.
184
- This creates a deliberate pause in the research workflow for quality decision-making.
185
-
186
- When to use:
187
- - After receiving search results: What key information did I find?
188
- - Before deciding next steps: Do I have enough to answer comprehensively?
189
- - When assessing research gaps: What specific information am I still missing?
190
- - Before concluding research: Can I provide a complete answer now?
187
+ // packages/toolbox/src/lib/state.ts
188
+ function toState(options) {
189
+ return options.experimental_context;
190
+ }
191
191
 
192
- Reflection should address:
193
- 1. Analysis of current findings - What concrete information have I gathered?
194
- 2. Gap assessment - What crucial information is still missing?
195
- 3. Quality evaluation - Do I have sufficient evidence/examples for a good answer?
196
- 4. Strategic decision - Should I continue searching or provide my answer?
197
- `,
192
+ // packages/toolbox/src/lib/filesystem.ts
193
+ var read_file_tool = tool4({
194
+ description: `Use this tool to read a file from the filesystem. Supports reading entire files or specific line ranges.`,
198
195
  inputSchema: z4.object({
199
- reflection: z4.string().describe("Your detailed reflection on research progress.")
196
+ filePath: z4.string().min(1),
197
+ lineStart: z4.coerce.number().int().positive().optional().describe(
198
+ "Starting line number (1-indexed). If omitted, reads from the beginning."
199
+ ),
200
+ lineEnd: z4.coerce.number().int().positive().optional().describe(
201
+ "Ending line number (1-indexed). If omitted, reads to the end. Maximum 200 lines can be read at once."
202
+ )
200
203
  }),
201
- execute: async ({ reflection }, options) => {
204
+ execute: async ({ filePath, lineStart, lineEnd }, options) => {
202
205
  const context = toState(options);
203
- context.scratchpad += `- ${reflection}
204
- `;
205
- return `Reflection recorded. Current scratchpad now:
206
- ---
207
- ${context.scratchpad}`;
206
+ const fullPath = join(context.repo_path, filePath);
207
+ const stats = await stat(fullPath);
208
+ const maxSize = 100 * 1024;
209
+ if (stats.size > maxSize) {
210
+ return `File too large to read (size: ${stats.size} bytes, max: ${maxSize} bytes)`;
211
+ }
212
+ try {
213
+ const content = await readFile(fullPath, "utf-8");
214
+ if (!lineStart && !lineEnd) {
215
+ return content;
216
+ }
217
+ const lines = content.split("\n");
218
+ const start = (lineStart ?? 1) - 1;
219
+ const end = lineEnd ?? lines.length;
220
+ if (start < 0 || start >= lines.length) {
221
+ return `Invalid lineStart: ${lineStart}. File has ${lines.length} lines.`;
222
+ }
223
+ if (end < start) {
224
+ return `Invalid range: lineEnd (${lineEnd}) must be >= lineStart (${lineStart})`;
225
+ }
226
+ const maxLines = 200;
227
+ const requestedLines = end - start;
228
+ if (requestedLines > maxLines) {
229
+ return `Too many lines requested (${requestedLines}). Maximum is ${maxLines} lines.`;
230
+ }
231
+ return lines.slice(start, end).join("\n");
232
+ } catch (error) {
233
+ console.error("Error reading file:", error);
234
+ return `Error reading file: ${JSON.stringify(error)}`;
235
+ }
208
236
  }
209
237
  });
210
-
211
- // packages/toolbox/src/lib/weather.ts
212
- import { tool as tool5 } from "ai";
213
- import { z as z5 } from "zod";
214
- var GetWeatherInputSchema = z5.object({
215
- location: z5.string().describe("City name, address or coordinates"),
216
- unit: z5.enum(["C", "F"]).default("C")
217
- });
218
- var GetWeatherSchema = z5.object({
219
- location: z5.string(),
220
- unit: z5.enum(["C", "F"]),
221
- temperature: z5.number(),
222
- condition: z5.string(),
223
- high: z5.number(),
224
- low: z5.number(),
225
- humidity: z5.number(),
226
- windKph: z5.number(),
227
- icon: z5.string().optional()
238
+ var read_dir_tool = tool4({
239
+ description: "Use this tool to list files in a directory.",
240
+ inputSchema: z4.object({
241
+ dir_path: z4.string().min(1).optional().default("./").describe('Relative path to the directory. Defaults to "./"')
242
+ }),
243
+ execute: async ({ dir_path }, options) => {
244
+ const context = toState(options);
245
+ return readdir(join(context.repo_path, dir_path), "utf-8");
246
+ }
228
247
  });
229
- var getWeatherTool = tool5({
230
- description: "Get the current weather for a location.",
231
- inputSchema: GetWeatherInputSchema,
232
- outputSchema: GetWeatherSchema,
233
- execute: async ({ location, unit }) => {
234
- const { latitude, longitude, name } = await geocodeLocation(location);
235
- const params = new URLSearchParams({
236
- latitude: String(latitude),
237
- longitude: String(longitude),
238
- current: [
239
- "temperature_2m",
240
- "relative_humidity_2m",
241
- "wind_speed_10m",
242
- "weather_code"
243
- ].join(","),
244
- daily: ["temperature_2m_max", "temperature_2m_min"].join(","),
245
- timezone: "auto",
246
- temperature_unit: unit === "F" ? "fahrenheit" : "celsius",
247
- wind_speed_unit: "kmh"
248
+ var glob_tool = tool4({
249
+ description: `Use this tool to list files in a directory.
250
+ This tool returns only direct files and directories that match the provided glob pattern.
251
+ Make sure not to look for framework generated files, such as ios/android folders in a react native project or .next in a nextjs project and so on.
252
+ `,
253
+ inputSchema: z4.object({
254
+ pattern: z4.string().min(1).describe("A glob pattern to match files. ")
255
+ }),
256
+ execute: async ({ pattern }, options) => {
257
+ const context = toState(options);
258
+ const files = await FastGlob(pattern, {
259
+ dot: true,
260
+ cwd: context.repo_path,
261
+ unique: true,
262
+ deep: 1,
263
+ followSymbolicLinks: false,
264
+ ignore: await ignorePatterns(context.repo_path)
248
265
  });
249
- const url = `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
250
- const res = await fetch(url);
251
- if (!res.ok) throw new Error(`Weather API failed: ${res.status}`);
252
- const data = await res.json();
253
- const current = data?.current;
254
- const daily = data?.daily;
255
- if (!current || !daily) throw new Error("Malformed weather API response");
256
- const weatherCode = Number(current.weather_code);
257
- const mapped = mapWeatherCode(weatherCode);
258
- const result = {
259
- location: name,
260
- unit,
261
- temperature: Math.round(Number(current.temperature_2m)),
262
- condition: mapped.condition,
263
- high: Math.round(Number(daily.temperature_2m_max?.[0])),
264
- low: Math.round(Number(daily.temperature_2m_min?.[0])),
265
- humidity: Math.max(
266
- 0,
267
- Math.min(1, Number(current.relative_humidity_2m) / 100)
268
- ),
269
- windKph: Math.round(Number(current.wind_speed_10m)),
270
- icon: mapped.icon
271
- };
272
- return result;
266
+ return files;
273
267
  }
274
268
  });
275
- async function geocodeLocation(location) {
276
- const coordMatch = location.trim().match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/);
277
- if (coordMatch) {
278
- const latitude = parseFloat(coordMatch[1]);
279
- const longitude = parseFloat(coordMatch[2]);
280
- return {
281
- latitude,
282
- longitude,
283
- name: `${latitude.toFixed(3)}, ${longitude.toFixed(3)}`
284
- };
285
- }
286
- const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
287
- location
288
- )}&count=1&language=en&format=json`;
289
- const res = await fetch(url);
290
- if (!res.ok) throw new Error(`Geocoding failed: ${res.status}`);
291
- const data = await res.json();
292
- const first = data?.results?.[0];
293
- if (!first) throw new Error(`Location not found: ${location}`);
294
- const nameParts = [first.name, first.admin1, first.country_code].filter(
295
- Boolean
296
- );
297
- return {
298
- latitude: first.latitude,
299
- longitude: first.longitude,
300
- name: nameParts.join(", ")
301
- };
302
- }
303
- function mapWeatherCode(code) {
304
- switch (code) {
305
- case 0:
306
- return { condition: "Clear sky", icon: "weather-sun" };
307
- case 1:
308
- return { condition: "Mainly clear", icon: "weather-sun" };
309
- case 2:
310
- return { condition: "Partly cloudy", icon: "weather-partly" };
311
- case 3:
312
- return { condition: "Overcast", icon: "weather-cloud" };
313
- case 45:
314
- case 48:
315
- return { condition: "Fog", icon: "weather-fog" };
316
- case 51:
317
- case 53:
318
- case 55:
319
- case 56:
320
- case 57:
321
- return { condition: "Drizzle", icon: "weather-drizzle" };
322
- case 61:
323
- case 63:
324
- case 65:
325
- case 66:
326
- case 67:
327
- return { condition: "Rain", icon: "weather-rain" };
328
- case 71:
329
- case 73:
330
- case 75:
331
- case 77:
332
- return { condition: "Snow", icon: "weather-snow" };
333
- case 80:
334
- case 81:
335
- case 82:
336
- return { condition: "Showers", icon: "weather-showers" };
337
- case 85:
338
- case 86:
339
- return { condition: "Snow showers", icon: "weather-snow" };
340
- case 95:
341
- case 96:
342
- case 99:
343
- return { condition: "Thunderstorm", icon: "weather-thunder" };
344
- default:
345
- return { condition: "Unknown" };
346
- }
347
- }
348
-
349
- // packages/toolbox/src/lib/web-search.ts
350
- import { groq } from "@ai-sdk/groq";
351
- import { agent } from "@deepagents/agent";
352
- var web_search_tool = duckDuckGoSearch;
353
- var searchAgent = agent({
354
- name: "research_agent",
355
- model: groq("openai/gpt-oss-20b"),
356
- prompt: "You are a diligent research assistant. Your task is to gather accurate and relevant information on a given topic using web search. Use the browser_search tool to find up-to-date information, and synthesize your findings into a concise summary.",
357
- tools: {
358
- browser_search: duckDuckGoSearch
269
+ var search_files_tool = tool4({
270
+ description: "Search for files in the repository by name or extension. Returns relative file paths. Avoids framework-generated and ignored paths.",
271
+ inputSchema: z4.object({
272
+ pattern: z4.string().min(1).describe("A glob pattern to match files."),
273
+ max_results: z4.number().int().positive().max(500).optional().default(100),
274
+ depth: z4.number().int().describe("Max directory depth to search. defaults to 31.")
275
+ }),
276
+ execute: async ({ pattern, max_results = 100, depth }, options) => {
277
+ const context = toState(options);
278
+ const ignore = await ignorePatterns(context.repo_path);
279
+ const files = await FastGlob(pattern, {
280
+ dot: false,
281
+ cwd: context.repo_path,
282
+ unique: true,
283
+ deep: depth,
284
+ followSymbolicLinks: false,
285
+ ignore
286
+ });
287
+ return files.slice(0, max_results);
359
288
  }
360
289
  });
361
-
362
- // packages/toolbox/src/lib/user-story-formatter.ts
363
- import { tool as tool6 } from "ai";
364
- import z6 from "zod";
365
- import { toState as toState2 } from "@deepagents/agent";
366
- var AcceptanceCriteriaSchema = z6.object({
367
- criterion: z6.string().describe("A specific, testable acceptance criterion")
368
- });
369
- var UserStorySchema = z6.object({
370
- title: z6.string().describe("Clear, concise title for the user story"),
371
- userRole: z6.string().describe('The user role or persona (e.g., "developer", "end user")'),
372
- action: z6.string().describe("What the user wants to do"),
373
- benefit: z6.string().describe("The value or benefit the user gets"),
374
- acceptanceCriteria: z6.array(AcceptanceCriteriaSchema).describe("List of specific, testable conditions that must be met"),
375
- technicalNotes: z6.string().optional().describe(
376
- "Relevant files, components, or dependencies from the repository"
377
- ),
378
- priority: z6.enum(["High", "Medium", "Low"]).describe("Priority level based on complexity and dependencies"),
379
- storyPoints: z6.enum(["1", "2", "3", "5", "8", "13"]).describe("Estimated complexity using Fibonacci sequence"),
380
- epicOrFeature: z6.string().optional().describe("The epic or feature group this story belongs to")
290
+ var file_exists_tool = tool4({
291
+ description: "Check if a file exists in the repository.",
292
+ inputSchema: z4.object({ filePath: z4.string().min(1) }),
293
+ execute: async ({ filePath }, options) => {
294
+ const context = toState(options);
295
+ return stat(join(context.repo_path, filePath)).then(() => true).catch(() => false);
296
+ }
381
297
  });
382
- var user_story_formatter_tool = tool6({
383
- description: `Tool for formatting and recording user stories in a standardized format.
384
-
385
- Use this tool to create well-structured user stories following product management best practices.
386
- Each story should follow the format: "As a [role], I want to [action], so that [benefit]"
387
-
388
- When to use:
389
- - After analyzing a feature or component in the codebase
390
- - When you've gathered enough information to write a complete user story
391
- - To document findings in a structured, actionable format
392
- - To maintain consistency across all generated user stories
393
-
394
- The tool will:
395
- 1. Format the story in the standard user story template
396
- 2. Store it in the context for later synthesis
397
- 3. Return a formatted version for immediate review
398
- `,
399
- inputSchema: UserStorySchema,
400
- execute: async (story, options) => {
401
- const context = toState2(options);
402
- context.userStories ??= [];
403
- context.userStories.push(story);
404
- const formatted = `
405
- ## ${story.title}
406
-
407
- **User Story:**
408
- As a **${story.userRole}**, I want to **${story.action}**, so that **${story.benefit}**.
409
-
410
- **Acceptance Criteria:**
411
- ${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac.criterion}`).join("\n")}
412
-
413
- **Technical Notes:**
414
- ${story.technicalNotes || "N/A"}
415
-
416
- **Priority:** ${story.priority}
417
- **Story Points:** ${story.storyPoints}
418
- ${story.epicOrFeature ? `**Epic/Feature:** ${story.epicOrFeature}` : ""}
419
-
420
- ---
421
- `.trim();
422
- return `User story recorded successfully!
423
-
424
- ${formatted}
425
-
426
- Total stories recorded: ${context.userStories.length}`;
298
+ var search_content_tool = dynamicTool({
299
+ description: `Use this tool to search the content of files in the repository. this tool is backed by a vector database and uses semantic search to find the most relevant snippets of content.`,
300
+ // inputSchema: z.object({
301
+ // query: z.string().min(1).describe('The search query.'),
302
+ // }),
303
+ inputSchema: jsonSchema({
304
+ additionalProperties: true,
305
+ type: "object",
306
+ properties: {
307
+ query: {
308
+ type: "string",
309
+ description: "The search query."
310
+ }
311
+ }
312
+ }),
313
+ async execute(input, options) {
314
+ const keys = Object.keys(input).filter((k) => k !== "query");
315
+ if (keys.length > 0) {
316
+ return `Invalid input: only "query" is supported. Found extra keys: ${keys.join(", ")}`;
317
+ }
318
+ if (typeof input.query !== "string" || input.query.trim().length === 0) {
319
+ return 'Invalid input: "query" must be a non-empty string.';
320
+ }
321
+ try {
322
+ const context = toState(options);
323
+ const results = await similaritySearch(input.query, {
324
+ connector: connectors.repo(
325
+ context.repo_path,
326
+ [".ts", ".tsx", ".md", ".prisma"],
327
+ "never"
328
+ ),
329
+ store: nodeSQLite("deepsearch.sqlite", 384),
330
+ embedder: fastembed()
331
+ });
332
+ const contents = results.map((it) => {
333
+ const source = relative(context.repo_path, it.document_id);
334
+ const filename = basename(source);
335
+ return {
336
+ source,
337
+ snippet: it.content,
338
+ citation: `[${filename}](./${source})`
339
+ };
340
+ });
341
+ return contents.map(
342
+ (it) => [
343
+ `File: ${it.source}`,
344
+ `Citation: ${it.citation}`,
345
+ "```",
346
+ it.snippet,
347
+ "```"
348
+ ].join("\n")
349
+ ).join("\n\n------\n\n");
350
+ } catch (error) {
351
+ console.error("Error in search_content_tool:", error);
352
+ return `Error during content search: ${JSON.stringify(error)}`;
353
+ }
427
354
  }
428
355
  });
356
+ async function repoTree(dir_path) {
357
+ const result = await spawn2("git", ["ls-tree", "-r", "HEAD", "--name-only"], {
358
+ cwd: dir_path
359
+ }).pipe("tree", ["--fromfile"]);
360
+ return result.output;
361
+ }
429
362
 
430
363
  // packages/toolbox/src/lib/hackernews-search.ts
431
- import { tool as tool7 } from "ai";
432
- import { z as z7 } from "zod";
433
- import { toState as toState3 } from "@deepagents/agent";
364
+ import { tool as tool5 } from "ai";
365
+ import { z as z5 } from "zod";
434
366
  function buildTags(options) {
435
367
  const tags = [];
436
368
  if (options.type && options.type !== "all") {
@@ -744,21 +676,21 @@ function formatJobResults(response) {
744
676
  `;
745
677
  return header + results.join("\n");
746
678
  }
747
- var search_by_query = tool7({
679
+ var search_by_query = tool5({
748
680
  description: "Tool to search HackerNews stories by keywords and filters. Use when you need to find HN stories matching specific search criteria, time periods, or popularity thresholds.",
749
- inputSchema: z7.object({
750
- query: z7.string().describe(
681
+ inputSchema: z5.object({
682
+ query: z5.string().describe(
751
683
  'Search query for story titles and content. Examples: "artificial intelligence", "python"'
752
684
  ),
753
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
754
- minPoints: z7.number().int().min(0).optional().describe("Minimum points threshold"),
755
- minComments: z7.number().int().min(0).optional().describe("Minimum comments threshold"),
756
- sortBy: z7.enum(["relevance", "date"]).default("relevance").describe("Sort results by relevance or date"),
757
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
758
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
685
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
686
+ minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
687
+ minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
688
+ sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort results by relevance or date"),
689
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
690
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
759
691
  }),
760
692
  execute: async (input, options) => {
761
- const context = toState3(options);
693
+ const context = toState(options);
762
694
  context.hackernews_sources ??= [];
763
695
  const numericFilters = buildNumericFilters({
764
696
  timeFilter: input.timeFilter,
@@ -777,19 +709,19 @@ var search_by_query = tool7({
777
709
  return formatStoryResults(response);
778
710
  }
779
711
  });
780
- var search_by_author = tool7({
712
+ var search_by_author = tool5({
781
713
  description: "Tool to search HackerNews content by author username. Use when you need to find all stories, comments, or both by a specific HN user.",
782
- inputSchema: z7.object({
783
- author: z7.string().describe('HackerNews username to search for. Examples: "pg", "tptacek"'),
784
- type: z7.enum(["story", "comment", "all"]).default("all").describe("Type of content: story, comment, or all"),
785
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
786
- minComments: z7.number().int().min(0).optional().describe("Minimum comments threshold (for stories only)"),
787
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort results by relevance or date"),
788
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
789
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
714
+ inputSchema: z5.object({
715
+ author: z5.string().describe('HackerNews username to search for. Examples: "pg", "tptacek"'),
716
+ type: z5.enum(["story", "comment", "all"]).default("all").describe("Type of content: story, comment, or all"),
717
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
718
+ minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold (for stories only)"),
719
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort results by relevance or date"),
720
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
721
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
790
722
  }),
791
723
  execute: async (input, options) => {
792
- const context = toState3(options);
724
+ const context = toState(options);
793
725
  context.hackernews_sources ??= [];
794
726
  const tags = buildTags({ type: input.type, author: input.author });
795
727
  const numericFilters = buildNumericFilters({
@@ -807,13 +739,13 @@ var search_by_author = tool7({
807
739
  return formatAuthorResults(response);
808
740
  }
809
741
  });
810
- var get_story_item = tool7({
742
+ var get_story_item = tool5({
811
743
  description: "Tool to get detailed information about a specific HackerNews story by ID. Use when you need full details about a particular HN story including title, URL, author, points, and comments.",
812
- inputSchema: z7.object({
813
- storyId: z7.string().describe('HackerNews story ID. Example: "38709478"')
744
+ inputSchema: z5.object({
745
+ storyId: z5.string().describe('HackerNews story ID. Example: "38709478"')
814
746
  }),
815
747
  execute: async (input, options) => {
816
- const context = toState3(options);
748
+ const context = toState(options);
817
749
  context.hackernews_sources ??= [];
818
750
  const item = await fetchHNItem(input.storyId);
819
751
  if (item.type !== "story") {
@@ -829,10 +761,10 @@ var get_story_item = tool7({
829
761
  return formatItemDetails(item);
830
762
  }
831
763
  });
832
- var get_story_comment = tool7({
764
+ var get_story_comment = tool5({
833
765
  description: "Tool to get detailed information about a specific HackerNews comment by ID. Use when you need full details about a particular comment including text, author, and parent story information.",
834
- inputSchema: z7.object({
835
- commentId: z7.string().describe('HackerNews comment ID. Example: "38710123"')
766
+ inputSchema: z5.object({
767
+ commentId: z5.string().describe('HackerNews comment ID. Example: "38710123"')
836
768
  }),
837
769
  execute: async (input) => {
838
770
  const item = await fetchHNItem(input.commentId);
@@ -844,14 +776,14 @@ var get_story_comment = tool7({
844
776
  return formatItemDetails(item);
845
777
  }
846
778
  });
847
- var get_front_page_stories = tool7({
779
+ var get_front_page_stories = tool5({
848
780
  description: "Tool to get current HackerNews front page stories. Use when you need to see the latest trending and popular stories on HN.",
849
- inputSchema: z7.object({
850
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
851
- hitsPerPage: z7.number().int().min(1).max(50).default(30).describe("Results per page (max 50)")
781
+ inputSchema: z5.object({
782
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
783
+ hitsPerPage: z5.number().int().min(1).max(50).default(30).describe("Results per page (max 50)")
852
784
  }),
853
785
  execute: async (input, options) => {
854
- const context = toState3(options);
786
+ const context = toState(options);
855
787
  context.hackernews_sources ??= [];
856
788
  const response = await searchHackerNewsAPI({
857
789
  tags: "front_page",
@@ -863,13 +795,13 @@ var get_front_page_stories = tool7({
863
795
  return formatStoryResults(response);
864
796
  }
865
797
  });
866
- var get_story_comments = tool7({
798
+ var get_story_comments = tool5({
867
799
  description: "Tool to get all comments for a specific HackerNews story. Use when you need to read the discussion and comments on a particular HN story.",
868
- inputSchema: z7.object({
869
- storyId: z7.string().describe('HackerNews story ID. Example: "38709478"'),
870
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort comments by relevance or date"),
871
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
872
- hitsPerPage: z7.number().int().min(1).max(1e3).default(50).describe("Results per page (max 1000)")
800
+ inputSchema: z5.object({
801
+ storyId: z5.string().describe('HackerNews story ID. Example: "38709478"'),
802
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort comments by relevance or date"),
803
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
804
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(50).describe("Results per page (max 1000)")
873
805
  }),
874
806
  execute: async (input) => {
875
807
  const response = await searchHackerNewsAPI({
@@ -881,21 +813,21 @@ var get_story_comments = tool7({
881
813
  return formatCommentResults(response);
882
814
  }
883
815
  });
884
- var search_ask_hn = tool7({
816
+ var search_ask_hn = tool5({
885
817
  description: "Tool to search Ask HN posts - questions posed to the HackerNews community. Use when you need to find community questions and discussions on specific topics.",
886
- inputSchema: z7.object({
887
- query: z7.string().default("").describe(
818
+ inputSchema: z5.object({
819
+ query: z5.string().default("").describe(
888
820
  'Search query for Ask HN posts. Examples: "artificial intelligence", "career"'
889
821
  ),
890
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
891
- minPoints: z7.number().int().min(0).optional().describe("Minimum points threshold"),
892
- minComments: z7.number().int().min(0).optional().describe("Minimum comments threshold"),
893
- sortBy: z7.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
894
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
895
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
822
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
823
+ minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
824
+ minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
825
+ sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
826
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
827
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
896
828
  }),
897
829
  execute: async (input, options) => {
898
- const context = toState3(options);
830
+ const context = toState(options);
899
831
  context.hackernews_sources ??= [];
900
832
  const numericFilters = buildNumericFilters({
901
833
  timeFilter: input.timeFilter,
@@ -914,21 +846,21 @@ var search_ask_hn = tool7({
914
846
  return formatStoryResults(response);
915
847
  }
916
848
  });
917
- var search_show_hn = tool7({
849
+ var search_show_hn = tool5({
918
850
  description: "Tool to search Show HN posts - projects and products shared with the HackerNews community. Use when you need to discover community projects, demos, or products on specific topics.",
919
- inputSchema: z7.object({
920
- query: z7.string().default("").describe(
851
+ inputSchema: z5.object({
852
+ query: z5.string().default("").describe(
921
853
  'Search query for Show HN posts. Examples: "web app", "open source"'
922
854
  ),
923
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
924
- minPoints: z7.number().int().min(0).optional().describe("Minimum points threshold"),
925
- minComments: z7.number().int().min(0).optional().describe("Minimum comments threshold"),
926
- sortBy: z7.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
927
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
928
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
855
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
856
+ minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
857
+ minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
858
+ sortBy: z5.enum(["relevance", "date"]).default("relevance").describe("Sort by relevance or date"),
859
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
860
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
929
861
  }),
930
862
  execute: async (input, options) => {
931
- const context = toState3(options);
863
+ const context = toState(options);
932
864
  context.hackernews_sources ??= [];
933
865
  const numericFilters = buildNumericFilters({
934
866
  timeFilter: input.timeFilter,
@@ -947,19 +879,19 @@ var search_show_hn = tool7({
947
879
  return formatStoryResults(response);
948
880
  }
949
881
  });
950
- var search_jobs = tool7({
882
+ var search_jobs = tool5({
951
883
  description: "Tool to search HackerNews job postings. Use when you need to find tech job opportunities posted on HN.",
952
- inputSchema: z7.object({
953
- query: z7.string().default("").describe(
884
+ inputSchema: z5.object({
885
+ query: z5.string().default("").describe(
954
886
  'Search query for job postings. Examples: "remote", "machine learning"'
955
887
  ),
956
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
957
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
958
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
959
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
888
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
889
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
890
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
891
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
960
892
  }),
961
893
  execute: async (input, options) => {
962
- const context = toState3(options);
894
+ const context = toState(options);
963
895
  context.hackernews_sources ??= [];
964
896
  const numericFilters = buildNumericFilters({
965
897
  timeFilter: input.timeFilter
@@ -976,17 +908,17 @@ var search_jobs = tool7({
976
908
  return formatJobResults(response);
977
909
  }
978
910
  });
979
- var search_polls = tool7({
911
+ var search_polls = tool5({
980
912
  description: "Tool to search HackerNews polls - community surveys and voting. Use when you need to find community polls on various topics.",
981
- inputSchema: z7.object({
982
- query: z7.string().default("").describe('Search query for polls. Example: "programming language"'),
983
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
984
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
985
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
986
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
913
+ inputSchema: z5.object({
914
+ query: z5.string().default("").describe('Search query for polls. Example: "programming language"'),
915
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
916
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
917
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
918
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
987
919
  }),
988
920
  execute: async (input, options) => {
989
- const context = toState3(options);
921
+ const context = toState(options);
990
922
  context.hackernews_sources ??= [];
991
923
  const numericFilters = buildNumericFilters({
992
924
  timeFilter: input.timeFilter
@@ -1003,29 +935,29 @@ var search_polls = tool7({
1003
935
  return formatStoryResults(response);
1004
936
  }
1005
937
  });
1006
- var get_user_profile = tool7({
938
+ var get_user_profile = tool5({
1007
939
  description: "Tool to get HackerNews user profile information. Use when you need to view a user's karma, account creation date, and bio.",
1008
- inputSchema: z7.object({
1009
- username: z7.string().describe('HackerNews username. Example: "pg"')
940
+ inputSchema: z5.object({
941
+ username: z5.string().describe('HackerNews username. Example: "pg"')
1010
942
  }),
1011
943
  execute: async (input) => {
1012
944
  const user = await fetchHNUser(input.username);
1013
945
  return formatUserProfile(user);
1014
946
  }
1015
947
  });
1016
- var search_by_domain = tool7({
948
+ var search_by_domain = tool5({
1017
949
  description: "Tool to search HackerNews stories from a specific domain or website. Use when you need to find all HN posts from a particular domain or track discussions about content from specific websites.",
1018
- inputSchema: z7.object({
1019
- domain: z7.string().describe('Domain to search. Examples: "github.com", "arxiv.org"'),
1020
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
1021
- minPoints: z7.number().int().min(0).optional().describe("Minimum points threshold"),
1022
- minComments: z7.number().int().min(0).optional().describe("Minimum comments threshold"),
1023
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
1024
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
1025
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
950
+ inputSchema: z5.object({
951
+ domain: z5.string().describe('Domain to search. Examples: "github.com", "arxiv.org"'),
952
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
953
+ minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
954
+ minComments: z5.number().int().min(0).optional().describe("Minimum comments threshold"),
955
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
956
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
957
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
1026
958
  }),
1027
959
  execute: async (input, options) => {
1028
- const context = toState3(options);
960
+ const context = toState(options);
1029
961
  context.hackernews_sources ??= [];
1030
962
  const numericFilters = buildNumericFilters({
1031
963
  timeFilter: input.timeFilter,
@@ -1056,21 +988,21 @@ var search_by_domain = tool7({
1056
988
  return formatStoryResults(data);
1057
989
  }
1058
990
  });
1059
- var search_highly_discussed = tool7({
991
+ var search_highly_discussed = tool5({
1060
992
  description: "Tool to search for highly discussed HackerNews stories with many comments. Use when you need to find engaging or controversial discussions with significant community participation.",
1061
- inputSchema: z7.object({
1062
- minComments: z7.number().int().min(1).describe(
993
+ inputSchema: z5.object({
994
+ minComments: z5.number().int().min(1).describe(
1063
995
  "Minimum number of comments (required). Example: 100 for highly discussed stories"
1064
996
  ),
1065
- query: z7.string().default("").describe('Optional search query. Example: "AI"'),
1066
- timeFilter: z7.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
1067
- minPoints: z7.number().int().min(0).optional().describe("Minimum points threshold"),
1068
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
1069
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
1070
- hitsPerPage: z7.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
997
+ query: z5.string().default("").describe('Optional search query. Example: "AI"'),
998
+ timeFilter: z5.enum(["d", "w", "m", "y", "all"]).default("all").describe("Time filter: d=day, w=week, m=month, y=year, all=no filter"),
999
+ minPoints: z5.number().int().min(0).optional().describe("Minimum points threshold"),
1000
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
1001
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
1002
+ hitsPerPage: z5.number().int().min(1).max(1e3).default(20).describe("Results per page (max 1000)")
1071
1003
  }),
1072
1004
  execute: async (input, options) => {
1073
- const context = toState3(options);
1005
+ const context = toState(options);
1074
1006
  context.hackernews_sources ??= [];
1075
1007
  const numericFilters = buildNumericFilters({
1076
1008
  timeFilter: input.timeFilter,
@@ -1089,20 +1021,20 @@ var search_highly_discussed = tool7({
1089
1021
  return formatStoryResults(response);
1090
1022
  }
1091
1023
  });
1092
- var search_trending = tool7({
1024
+ var search_trending = tool5({
1093
1025
  description: "Tool to find currently trending HackerNews stories - recent posts with high engagement. Use when you need to discover what's hot right now on HN by combining recency with high points and comments.",
1094
- inputSchema: z7.object({
1095
- minPoints: z7.number().int().min(10).default(50).describe(
1026
+ inputSchema: z5.object({
1027
+ minPoints: z5.number().int().min(10).default(50).describe(
1096
1028
  "Minimum points threshold. Example: 100 for highly upvoted stories"
1097
1029
  ),
1098
- maxAgeHours: z7.number().int().min(1).max(72).default(24).describe("Maximum age in hours. Example: 24 for stories from today"),
1099
- minComments: z7.number().int().min(0).default(10).describe("Minimum comments threshold"),
1100
- sortBy: z7.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
1101
- page: z7.number().int().min(0).default(0).describe("Page number (0-indexed)"),
1102
- hitsPerPage: z7.number().int().min(1).max(100).default(30).describe("Results per page (max 100)")
1030
+ maxAgeHours: z5.number().int().min(1).max(72).default(24).describe("Maximum age in hours. Example: 24 for stories from today"),
1031
+ minComments: z5.number().int().min(0).default(10).describe("Minimum comments threshold"),
1032
+ sortBy: z5.enum(["relevance", "date"]).default("date").describe("Sort by relevance or date"),
1033
+ page: z5.number().int().min(0).default(0).describe("Page number (0-indexed)"),
1034
+ hitsPerPage: z5.number().int().min(1).max(100).default(30).describe("Results per page (max 100)")
1103
1035
  }),
1104
1036
  execute: async (input, options) => {
1105
- const context = toState3(options);
1037
+ const context = toState(options);
1106
1038
  context.hackernews_sources ??= [];
1107
1039
  const numericFilters = buildNumericFilters({
1108
1040
  maxAgeHours: input.maxAgeHours,
@@ -1147,25 +1079,273 @@ var hackernewsTools = {
1147
1079
  search_highly_discussed,
1148
1080
  search_trending
1149
1081
  };
1082
+
1083
+ // packages/toolbox/src/lib/scratchbad.ts
1084
+ import { tool as tool6 } from "ai";
1085
+ import z6 from "zod";
1086
+ var scratchpad_tool = tool6({
1087
+ description: `Tool for strategic reflection on research progress and decision-making.
1088
+
1089
+ Use this tool after each search to analyze results and plan next steps systematically.
1090
+ This creates a deliberate pause in the research workflow for quality decision-making.
1091
+
1092
+ When to use:
1093
+ - After receiving search results: What key information did I find?
1094
+ - Before deciding next steps: Do I have enough to answer comprehensively?
1095
+ - When assessing research gaps: What specific information am I still missing?
1096
+ - Before concluding research: Can I provide a complete answer now?
1097
+
1098
+ Reflection should address:
1099
+ 1. Analysis of current findings - What concrete information have I gathered?
1100
+ 2. Gap assessment - What crucial information is still missing?
1101
+ 3. Quality evaluation - Do I have sufficient evidence/examples for a good answer?
1102
+ 4. Strategic decision - Should I continue searching or provide my answer?
1103
+ `,
1104
+ inputSchema: z6.object({
1105
+ reflection: z6.string().describe("Your detailed reflection on research progress.")
1106
+ }),
1107
+ execute: async ({ reflection }, options) => {
1108
+ const context = toState(options);
1109
+ context.scratchpad += `- ${reflection}
1110
+ `;
1111
+ return `Reflection recorded. Current scratchpad now:
1112
+ ---
1113
+ ${context.scratchpad}`;
1114
+ }
1115
+ });
1116
+
1117
+ // packages/toolbox/src/lib/user-story-formatter.ts
1118
+ import { tool as tool7 } from "ai";
1119
+ import z7 from "zod";
1120
+ var AcceptanceCriteriaSchema = z7.object({
1121
+ criterion: z7.string().describe("A specific, testable acceptance criterion")
1122
+ });
1123
+ var UserStorySchema = z7.object({
1124
+ title: z7.string().describe("Clear, concise title for the user story"),
1125
+ userRole: z7.string().describe('The user role or persona (e.g., "developer", "end user")'),
1126
+ action: z7.string().describe("What the user wants to do"),
1127
+ benefit: z7.string().describe("The value or benefit the user gets"),
1128
+ acceptanceCriteria: z7.array(AcceptanceCriteriaSchema).describe("List of specific, testable conditions that must be met"),
1129
+ technicalNotes: z7.string().optional().describe(
1130
+ "Relevant files, components, or dependencies from the repository"
1131
+ ),
1132
+ priority: z7.enum(["High", "Medium", "Low"]).describe("Priority level based on complexity and dependencies"),
1133
+ storyPoints: z7.enum(["1", "2", "3", "5", "8", "13"]).describe("Estimated complexity using Fibonacci sequence"),
1134
+ epicOrFeature: z7.string().optional().describe("The epic or feature group this story belongs to")
1135
+ });
1136
+ var user_story_formatter_tool = tool7({
1137
+ description: `Tool for formatting and recording user stories in a standardized format.
1138
+
1139
+ Use this tool to create well-structured user stories following product management best practices.
1140
+ Each story should follow the format: "As a [role], I want to [action], so that [benefit]"
1141
+
1142
+ When to use:
1143
+ - After analyzing a feature or component in the codebase
1144
+ - When you've gathered enough information to write a complete user story
1145
+ - To document findings in a structured, actionable format
1146
+ - To maintain consistency across all generated user stories
1147
+
1148
+ The tool will:
1149
+ 1. Format the story in the standard user story template
1150
+ 2. Store it in the context for later synthesis
1151
+ 3. Return a formatted version for immediate review
1152
+ `,
1153
+ inputSchema: UserStorySchema,
1154
+ execute: async (story, options) => {
1155
+ const context = toState(options);
1156
+ context.userStories ??= [];
1157
+ context.userStories.push(story);
1158
+ const formatted = `
1159
+ ## ${story.title}
1160
+
1161
+ **User Story:**
1162
+ As a **${story.userRole}**, I want to **${story.action}**, so that **${story.benefit}**.
1163
+
1164
+ **Acceptance Criteria:**
1165
+ ${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac.criterion}`).join("\n")}
1166
+
1167
+ **Technical Notes:**
1168
+ ${story.technicalNotes || "N/A"}
1169
+
1170
+ **Priority:** ${story.priority}
1171
+ **Story Points:** ${story.storyPoints}
1172
+ ${story.epicOrFeature ? `**Epic/Feature:** ${story.epicOrFeature}` : ""}
1173
+
1174
+ ---
1175
+ `.trim();
1176
+ return `User story recorded successfully!
1177
+
1178
+ ${formatted}
1179
+
1180
+ Total stories recorded: ${context.userStories.length}`;
1181
+ }
1182
+ });
1183
+
1184
+ // packages/toolbox/src/lib/weather.ts
1185
+ import { tool as tool8 } from "ai";
1186
+ import { z as z8 } from "zod";
1187
+ var GetWeatherInputSchema = z8.object({
1188
+ location: z8.string().describe("City name, address or coordinates"),
1189
+ unit: z8.enum(["C", "F"]).default("C")
1190
+ });
1191
+ var GetWeatherSchema = z8.object({
1192
+ location: z8.string(),
1193
+ unit: z8.enum(["C", "F"]),
1194
+ temperature: z8.number(),
1195
+ condition: z8.string(),
1196
+ high: z8.number(),
1197
+ low: z8.number(),
1198
+ humidity: z8.number(),
1199
+ windKph: z8.number(),
1200
+ icon: z8.string().optional()
1201
+ });
1202
+ var getWeatherTool = tool8({
1203
+ description: "Get the current weather for a location.",
1204
+ inputSchema: GetWeatherInputSchema,
1205
+ outputSchema: GetWeatherSchema,
1206
+ execute: async ({ location, unit }) => {
1207
+ const { latitude, longitude, name } = await geocodeLocation(location);
1208
+ const params = new URLSearchParams({
1209
+ latitude: String(latitude),
1210
+ longitude: String(longitude),
1211
+ current: [
1212
+ "temperature_2m",
1213
+ "relative_humidity_2m",
1214
+ "wind_speed_10m",
1215
+ "weather_code"
1216
+ ].join(","),
1217
+ daily: ["temperature_2m_max", "temperature_2m_min"].join(","),
1218
+ timezone: "auto",
1219
+ temperature_unit: unit === "F" ? "fahrenheit" : "celsius",
1220
+ wind_speed_unit: "kmh"
1221
+ });
1222
+ const url = `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
1223
+ const res = await fetch(url);
1224
+ if (!res.ok) throw new Error(`Weather API failed: ${res.status}`);
1225
+ const data = await res.json();
1226
+ const current = data?.current;
1227
+ const daily = data?.daily;
1228
+ if (!current || !daily) throw new Error("Malformed weather API response");
1229
+ const weatherCode = Number(current.weather_code);
1230
+ const mapped = mapWeatherCode(weatherCode);
1231
+ const result = {
1232
+ location: name,
1233
+ unit,
1234
+ temperature: Math.round(Number(current.temperature_2m)),
1235
+ condition: mapped.condition,
1236
+ high: Math.round(Number(daily.temperature_2m_max?.[0])),
1237
+ low: Math.round(Number(daily.temperature_2m_min?.[0])),
1238
+ humidity: Math.max(
1239
+ 0,
1240
+ Math.min(1, Number(current.relative_humidity_2m) / 100)
1241
+ ),
1242
+ windKph: Math.round(Number(current.wind_speed_10m)),
1243
+ icon: mapped.icon
1244
+ };
1245
+ return result;
1246
+ }
1247
+ });
1248
+ async function geocodeLocation(location) {
1249
+ const coordMatch = location.trim().match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/);
1250
+ if (coordMatch) {
1251
+ const latitude = parseFloat(coordMatch[1]);
1252
+ const longitude = parseFloat(coordMatch[2]);
1253
+ return {
1254
+ latitude,
1255
+ longitude,
1256
+ name: `${latitude.toFixed(3)}, ${longitude.toFixed(3)}`
1257
+ };
1258
+ }
1259
+ const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
1260
+ location
1261
+ )}&count=1&language=en&format=json`;
1262
+ const res = await fetch(url);
1263
+ if (!res.ok) throw new Error(`Geocoding failed: ${res.status}`);
1264
+ const data = await res.json();
1265
+ const first = data?.results?.[0];
1266
+ if (!first) throw new Error(`Location not found: ${location}`);
1267
+ const nameParts = [first.name, first.admin1, first.country_code].filter(
1268
+ Boolean
1269
+ );
1270
+ return {
1271
+ latitude: first.latitude,
1272
+ longitude: first.longitude,
1273
+ name: nameParts.join(", ")
1274
+ };
1275
+ }
1276
+ function mapWeatherCode(code) {
1277
+ switch (code) {
1278
+ case 0:
1279
+ return { condition: "Clear sky", icon: "weather-sun" };
1280
+ case 1:
1281
+ return { condition: "Mainly clear", icon: "weather-sun" };
1282
+ case 2:
1283
+ return { condition: "Partly cloudy", icon: "weather-partly" };
1284
+ case 3:
1285
+ return { condition: "Overcast", icon: "weather-cloud" };
1286
+ case 45:
1287
+ case 48:
1288
+ return { condition: "Fog", icon: "weather-fog" };
1289
+ case 51:
1290
+ case 53:
1291
+ case 55:
1292
+ case 56:
1293
+ case 57:
1294
+ return { condition: "Drizzle", icon: "weather-drizzle" };
1295
+ case 61:
1296
+ case 63:
1297
+ case 65:
1298
+ case 66:
1299
+ case 67:
1300
+ return { condition: "Rain", icon: "weather-rain" };
1301
+ case 71:
1302
+ case 73:
1303
+ case 75:
1304
+ case 77:
1305
+ return { condition: "Snow", icon: "weather-snow" };
1306
+ case 80:
1307
+ case 81:
1308
+ case 82:
1309
+ return { condition: "Showers", icon: "weather-showers" };
1310
+ case 85:
1311
+ case 86:
1312
+ return { condition: "Snow showers", icon: "weather-snow" };
1313
+ case 95:
1314
+ case 96:
1315
+ case 99:
1316
+ return { condition: "Thunderstorm", icon: "weather-thunder" };
1317
+ default:
1318
+ return { condition: "Unknown" };
1319
+ }
1320
+ }
1321
+
1322
+ // packages/toolbox/src/lib/web-search.ts
1323
+ var web_search_tool = duckDuckGoSearch;
1150
1324
  export {
1151
1325
  GetWeatherSchema,
1152
1326
  ddgSearchSchema,
1153
1327
  duckDuckGoSearch,
1154
1328
  duckStocks,
1155
1329
  execute_os_command,
1330
+ file_exists_tool,
1156
1331
  getWeatherTool,
1157
1332
  get_front_page_stories,
1158
1333
  get_story_comment,
1159
1334
  get_story_comments,
1160
1335
  get_story_item,
1161
1336
  get_user_profile,
1337
+ glob_tool,
1162
1338
  hackernewsTools,
1339
+ read_dir_tool,
1340
+ read_file_tool,
1341
+ repoTree,
1163
1342
  scratchpad_tool,
1164
- searchAgent,
1165
1343
  search_ask_hn,
1166
1344
  search_by_author,
1167
1345
  search_by_domain,
1168
1346
  search_by_query,
1347
+ search_content_tool,
1348
+ search_files_tool,
1169
1349
  search_highly_discussed,
1170
1350
  search_jobs,
1171
1351
  search_polls,