code-ollama 0.12.0 → 0.13.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.
@@ -1213,6 +1213,9 @@ function Header({ model, onLoad }) {
1213
1213
  function ModelPicker({ currentModel, onSelect, onClose }) {
1214
1214
  const [options, setOptions] = useState([]);
1215
1215
  const [error, setError] = useState(null);
1216
+ const handleChange = useCallback((model) => {
1217
+ onSelect({ model });
1218
+ }, [onSelect]);
1216
1219
  useInput(async (_input, key) => {
1217
1220
  if (!error && options.length && key.return) {
1218
1221
  await tick();
@@ -1245,7 +1248,7 @@ function ModelPicker({ currentModel, onSelect, onClose }) {
1245
1248
  return /* @__PURE__ */ jsx(SelectPrompt, {
1246
1249
  options,
1247
1250
  defaultValue: currentModel,
1248
- onChange: onSelect,
1251
+ onChange: handleChange,
1249
1252
  onCancel: onClose,
1250
1253
  children: /* @__PURE__ */ jsx(SelectPromptHint, { message: "Select a model" })
1251
1254
  });
@@ -1290,7 +1293,7 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1290
1293
  setView(View.Edit);
1291
1294
  break;
1292
1295
  case Action.Clear:
1293
- onSave(void 0);
1296
+ onSave({ searxngBaseUrl: void 0 });
1294
1297
  break;
1295
1298
  case Action.Cancel:
1296
1299
  default: onClose();
@@ -1312,7 +1315,7 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1312
1315
  setError("URL must use http or https.");
1313
1316
  return;
1314
1317
  }
1315
- onSave(url.toString());
1318
+ onSave({ searxngBaseUrl: url.toString() });
1316
1319
  } catch {
1317
1320
  setError("Enter a valid URL.");
1318
1321
  }
@@ -1368,12 +1371,11 @@ var SCREEN = /* @__PURE__ */ function(SCREEN) {
1368
1371
  }(SCREEN || {});
1369
1372
  function App() {
1370
1373
  const { exit } = useApp();
1371
- const [appConfig, setAppConfig] = useState(() => loadConfig());
1374
+ const [appConfig, setConfig] = useState(() => loadConfig());
1372
1375
  const [currentScreen, setScreen] = useState(SCREEN.CHAT);
1373
1376
  const [mode, setMode] = useState(SAFE);
1374
1377
  const [sessionId, setSessionId] = useState(0);
1375
1378
  const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
1376
- const { model, searxngBaseUrl } = appConfig;
1377
1379
  const handleHeaderLoad = useCallback(() => {
1378
1380
  setIsHeaderLoaded(true);
1379
1381
  }, []);
@@ -1396,20 +1398,12 @@ function App() {
1396
1398
  break;
1397
1399
  }
1398
1400
  }, [exit]);
1399
- const handleSelect = useCallback((selected) => {
1400
- setAppConfig((currentConfig) => ({
1401
- ...currentConfig,
1402
- model: selected
1403
- }));
1404
- saveConfig({ model: selected });
1405
- setScreen(SCREEN.CHAT);
1406
- }, []);
1407
- const handleSaveSearch = useCallback((url) => {
1408
- setAppConfig((currentConfig) => ({
1409
- ...currentConfig,
1410
- searxngBaseUrl: url
1401
+ const handleUpdateConfig = useCallback((update) => {
1402
+ setConfig((current) => ({
1403
+ ...current,
1404
+ ...update
1411
1405
  }));
1412
- saveConfig({ searxngBaseUrl: url });
1406
+ saveConfig(update);
1413
1407
  setScreen(SCREEN.CHAT);
1414
1408
  }, []);
1415
1409
  const handleClose = useCallback(() => {
@@ -1429,21 +1423,21 @@ function App() {
1429
1423
  switch (currentScreen) {
1430
1424
  case SCREEN.MODEL_PICKER:
1431
1425
  screenContent = /* @__PURE__ */ jsx(ModelPicker, {
1432
- currentModel: model,
1433
- onSelect: handleSelect,
1426
+ currentModel: appConfig.model,
1427
+ onSelect: handleUpdateConfig,
1434
1428
  onClose: handleClose
1435
1429
  });
1436
1430
  break;
1437
1431
  case SCREEN.SEARCH_SETTINGS:
1438
1432
  screenContent = /* @__PURE__ */ jsx(SearchSettings, {
1439
- currentUrl: searxngBaseUrl,
1440
- onSave: handleSaveSearch,
1433
+ currentUrl: appConfig.searxngBaseUrl,
1434
+ onSave: handleUpdateConfig,
1441
1435
  onClose: handleClose
1442
1436
  });
1443
1437
  break;
1444
1438
  case SCREEN.CHAT:
1445
1439
  screenContent = /* @__PURE__ */ jsx(Chat, {
1446
- model,
1440
+ model: appConfig.model,
1447
1441
  onCommand: handleCommand,
1448
1442
  mode,
1449
1443
  onModeChange: setMode,
@@ -1455,13 +1449,13 @@ function App() {
1455
1449
  flexDirection: "column",
1456
1450
  children: [
1457
1451
  /* @__PURE__ */ jsx(Header, {
1458
- model,
1452
+ model: appConfig.model,
1459
1453
  onLoad: handleHeaderLoad
1460
1454
  }),
1461
1455
  isHeaderLoaded && screenContent,
1462
1456
  /* @__PURE__ */ jsx(Footer, {
1463
1457
  mode,
1464
- model,
1458
+ model: appConfig.model,
1465
1459
  onToggleMode: handleToggleMode
1466
1460
  })
1467
1461
  ]
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { homedir } from "node:os";
7
7
  import { Ollama } from "ollama";
8
8
  //#region package.json
9
9
  var name = "code-ollama";
10
- var version = "0.12.0";
10
+ var version = "0.13.0";
11
11
  //#endregion
12
12
  //#region src/constants/package.ts
13
13
  var NAME = name;
@@ -68,6 +68,7 @@ var LIST_DIR = "list_dir";
68
68
  var GREP_SEARCH = "grep_search";
69
69
  var VIEW_RANGE = "view_range";
70
70
  var WEB_SEARCH = "web_search";
71
+ var WEB_FETCH = "web_fetch";
71
72
  //#endregion
72
73
  //#region src/utils/agents.ts
73
74
  var AGENTS_FILE = "AGENTS.md";
@@ -282,14 +283,19 @@ var TOOLS = [
282
283
  defineTool(WEB_SEARCH, "Search the web for external or current information", { query: {
283
284
  type: "string",
284
285
  description: "The search query to look up"
285
- } }, ["query"])
286
+ } }, ["query"]),
287
+ defineTool(WEB_FETCH, "Fetch the readable content of a webpage at the given URL", { url: {
288
+ type: "string",
289
+ description: "The full URL of the page to fetch"
290
+ } }, ["url"])
286
291
  ];
287
292
  var READ_TOOLS = new Set([
288
293
  READ_FILE,
289
294
  LIST_DIR,
290
295
  GREP_SEARCH,
291
296
  VIEW_RANGE,
292
- WEB_SEARCH
297
+ WEB_SEARCH,
298
+ WEB_FETCH
293
299
  ]);
294
300
  var WRITE_TOOLS = new Set([
295
301
  WRITE_FILE,
@@ -444,13 +450,14 @@ async function grepSearch(pattern, dirPath) {
444
450
  //#endregion
445
451
  //#region src/utils/tools/web/fetch.ts
446
452
  var FETCH_TIMEOUT_MS = 1e4;
453
+ var BASE_HEADERS = { "user-agent": `${NAME}/${VERSION}` };
447
454
  /**
448
455
  * Fetch text from URL with timeout and headers
449
456
  */
450
457
  async function fetchText(url, headers) {
451
458
  const response = await fetch(url, {
452
459
  headers: {
453
- "user-agent": `${NAME}/${VERSION}`,
460
+ ...BASE_HEADERS,
454
461
  ...headers
455
462
  },
456
463
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
@@ -458,6 +465,21 @@ async function fetchText(url, headers) {
458
465
  if (!response.ok) throw new Error(`HTTP ${response.status.toString()}`);
459
466
  return response.text();
460
467
  }
468
+ /**
469
+ * Fetch and parse JSON from URL with timeout and headers
470
+ */
471
+ async function fetchJSON(url, headers = {}) {
472
+ const response = await fetch(url, {
473
+ headers: {
474
+ ...BASE_HEADERS,
475
+ Accept: "application/json",
476
+ ...headers
477
+ },
478
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
479
+ });
480
+ if (!response.ok) throw new Error(`HTTP ${response.status.toString()}`);
481
+ return response.json();
482
+ }
461
483
  //#endregion
462
484
  //#region src/utils/tools/web/utils.ts
463
485
  /**
@@ -485,6 +507,30 @@ function truncate(value, maxLength) {
485
507
  return value.length > maxLength ? `${value.slice(0, maxLength - 1).trimEnd()}…` : value;
486
508
  }
487
509
  //#endregion
510
+ //#region src/utils/tools/web/fetch-page.ts
511
+ var JINA_READER_BASE_URL = "https://r.jina.ai/";
512
+ /**
513
+ * Fetch readable page content via Jina Reader, with fallback to direct fetch + HTML stripping
514
+ */
515
+ async function webFetch(url) {
516
+ const trimmedUrl = url.trim();
517
+ if (!trimmedUrl) return {
518
+ content: "",
519
+ error: "URL cannot be empty"
520
+ };
521
+ try {
522
+ return { content: await fetchText(`${JINA_READER_BASE_URL}${trimmedUrl}`, { Accept: "text/plain" }) };
523
+ } catch {}
524
+ try {
525
+ return { content: `Note: Jina Reader unavailable, falling back to raw fetch.\n\n${cleanText(stripTags(await fetchText(trimmedUrl, { Accept: "text/html" })))}` };
526
+ } catch (error) {
527
+ return {
528
+ content: "",
529
+ error: `Failed to fetch page: ${error instanceof Error ? error.message : String(error)}`
530
+ };
531
+ }
532
+ }
533
+ //#endregion
488
534
  //#region src/utils/tools/web/search.ts
489
535
  var SEARCH_RESULT_LIMIT = 5;
490
536
  async function webSearch(query) {
@@ -496,7 +542,7 @@ async function webSearch(query) {
496
542
  const { searxngBaseUrl } = loadConfig();
497
543
  let searxngIssue = null;
498
544
  if (searxngBaseUrl) try {
499
- const searxngResults = await searchSearxng(searxngBaseUrl, trimmedQuery);
545
+ const searxngResults = await searchSearXNG(searxngBaseUrl, trimmedQuery);
500
546
  if (searxngResults.length) return { content: formatSearchResults("SearXNG", searxngResults) };
501
547
  searxngIssue = "SearXNG returned no results";
502
548
  } catch (error) {
@@ -515,14 +561,13 @@ async function webSearch(query) {
515
561
  };
516
562
  }
517
563
  }
518
- async function searchSearxng(baseUrl, query) {
564
+ async function searchSearXNG(baseUrl, query) {
519
565
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
520
566
  const url = new URL(`${normalizedBaseUrl}/search`);
521
567
  url.searchParams.set("q", query);
522
568
  url.searchParams.set("format", "json");
523
569
  url.searchParams.set("language", "en-US");
524
- const response = await fetchText(url.toString(), { Accept: "application/json" });
525
- return normalizeResults(JSON.parse(response).results?.map((result) => ({
570
+ return normalizeResults((await fetchJSON(url.toString())).results?.map((result) => ({
526
571
  title: result.title ?? "",
527
572
  url: result.url ?? "",
528
573
  snippet: result.content ?? ""
@@ -594,6 +639,7 @@ async function executeTool(name, args, options) {
594
639
  case GREP_SEARCH: return await grepSearch(args.pattern, args.path);
595
640
  case VIEW_RANGE: return viewRange(args.path, args.start, args.end);
596
641
  case WEB_SEARCH: return await webSearch(args.query);
642
+ case WEB_FETCH: return await webFetch(args.url);
597
643
  default: return {
598
644
  content: "",
599
645
  error: `Unknown tool: ${name}`
@@ -650,7 +696,7 @@ async function processRunStream(messages, model) {
650
696
  }
651
697
  async function main(args = process.argv.slice(2)) {
652
698
  if (!args.length) {
653
- const { renderApp } = await import("./assets/tui-BSnwVbDN.js");
699
+ const { renderApp } = await import("./assets/tui-ByqNs9kx.js");
654
700
  reset();
655
701
  renderApp();
656
702
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Ollama coding agent that runs in your terminal",
5
5
  "author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
6
6
  "type": "module",