code-ollama 0.14.1 → 0.14.2

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.
@@ -7,18 +7,24 @@ import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
7
7
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Select, Spinner } from "@inkjs/ui";
10
- import { marked } from "marked";
10
+ import { Marked } from "marked";
11
11
  import { markedTerminal } from "marked-terminal";
12
12
  //#region src/components/CodeBlock/CodeBlock.tsx
13
13
  var highlightCache = /* @__PURE__ */ new Map();
14
- var CODE_BLOCK_REGEX = /^(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\1[ \t]*$/gm;
14
+ var CODE_BLOCK_REGEX = /^(?<indent>[ \t]*)(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\k<indent>\2[ \t]*$/gm;
15
+ function normalizeCodeBlockContent(content, indent = "") {
16
+ if (!indent) return content.trim();
17
+ const indentPattern = new RegExp(`^${indent}`, "gm");
18
+ return content.replace(indentPattern, "").trim();
19
+ }
15
20
  async function prewarmCodeBlocks(content) {
16
21
  const promises = [];
17
22
  let match;
18
23
  CODE_BLOCK_REGEX.lastIndex = 0;
19
24
  while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
20
- const language = match[2];
21
- const code = match[3].trim();
25
+ const indent = match[1];
26
+ const language = match[3];
27
+ const code = normalizeCodeBlockContent(match[4], indent);
22
28
  // v8 ignore next 2
23
29
  if (code) promises.push(prewarmHighlight(code, language));
24
30
  }
@@ -165,15 +171,29 @@ var inlineMathExtension = {
165
171
  //#endregion
166
172
  //#region src/components/Markdown/Markdown.tsx
167
173
  var HR_PLACEHOLDER = "__CODE_OLLAMA_HR_PLACEHOLDER__";
168
- marked.use(markedTerminal({ theme: "gitHub" }));
169
- marked.use({
170
- extensions: [inlineMathExtension],
171
- renderer: { hr: () => `${HR_PLACEHOLDER}\n` }
172
- });
173
174
  function renderMarkdown(content, hrWidth) {
174
175
  const hr = "─".repeat(Math.max(1, hrWidth));
176
+ const markdown = new Marked();
177
+ const rendererExtension = {
178
+ extensions: [inlineMathExtension],
179
+ useNewRenderer: true,
180
+ renderer: {
181
+ hr: () => `${HR_PLACEHOLDER}\n`,
182
+ text(token) {
183
+ const textToken = token;
184
+ if (typeof token === "object" && Array.isArray(textToken.tokens)) return this.parser.parseInline(textToken.tokens);
185
+ return String(textToken.text);
186
+ }
187
+ }
188
+ };
189
+ markdown.use(markedTerminal({
190
+ theme: "gitHub",
191
+ reflowText: true,
192
+ width: Math.max(1, hrWidth)
193
+ }));
194
+ markdown.use(rendererExtension);
175
195
  try {
176
- const result = marked.parse(content);
196
+ const result = markdown.parse(content);
177
197
  return (typeof result === "string" ? result.trim() : content).replaceAll(HR_PLACEHOLDER, hr);
178
198
  } catch {
179
199
  return content;
@@ -197,6 +217,101 @@ var TURN_ABORTED_MESSAGE = [
197
217
  "</turn_aborted>"
198
218
  ].join("\n");
199
219
  //#endregion
220
+ //#region src/components/Messages/utils.ts
221
+ function isWordCharacter(char) {
222
+ return char !== void 0 && /[A-Za-z0-9]/.test(char);
223
+ }
224
+ function isEscaped(content, index) {
225
+ let slashCount = 0;
226
+ for (let cursor = index - 1; cursor >= 0 && content[cursor] === "\\"; cursor--) slashCount += 1;
227
+ return slashCount % 2 === 1;
228
+ }
229
+ function canOpenEmphasis(content, index, length) {
230
+ const previous = content[index - 1];
231
+ const next = content[index + length];
232
+ if (!next || /\s/.test(next)) return false;
233
+ return !isWordCharacter(previous);
234
+ }
235
+ function canCloseEmphasis(content, index, length) {
236
+ const previous = content[index - 1];
237
+ const next = content[index + length];
238
+ if (!previous || /\s/.test(previous)) return false;
239
+ return !isWordCharacter(next);
240
+ }
241
+ function findUnmatchedInlineDelimiter(content) {
242
+ const stack = [];
243
+ for (let index = 0; index < content.length; index += 1) {
244
+ const current = content[index];
245
+ if (isEscaped(content, index)) continue;
246
+ const top = stack.at(-1);
247
+ if (top?.kind === "code") {
248
+ if (current === "`") stack.pop();
249
+ continue;
250
+ }
251
+ if (top?.kind === "latex") {
252
+ if (current === "$") stack.pop();
253
+ continue;
254
+ }
255
+ if (current === "`") {
256
+ stack.push({
257
+ index,
258
+ length: 1,
259
+ kind: "code",
260
+ marker: "`"
261
+ });
262
+ continue;
263
+ }
264
+ if (current === "$") {
265
+ stack.push({
266
+ index,
267
+ length: 1,
268
+ kind: "latex",
269
+ marker: "$"
270
+ });
271
+ continue;
272
+ }
273
+ if (current !== "*") continue;
274
+ const marker = current;
275
+ const length = content[index + 1] === marker ? 2 : 1;
276
+ const token = marker.repeat(length);
277
+ const kind = length === 2 ? "bold" : "italic";
278
+ if (top?.marker === token && top.kind === kind && canCloseEmphasis(content, index, length)) {
279
+ stack.pop();
280
+ if (length === 2) index += 1;
281
+ continue;
282
+ }
283
+ if (canOpenEmphasis(content, index, length)) {
284
+ stack.push({
285
+ index,
286
+ length,
287
+ kind,
288
+ marker: token
289
+ });
290
+ if (length === 2) index += 1;
291
+ }
292
+ }
293
+ return stack[0] ?? null;
294
+ }
295
+ function splitStreamingInlineContent(content) {
296
+ const unmatched = findUnmatchedInlineDelimiter(content);
297
+ if (!unmatched) return [{
298
+ type: "markdown",
299
+ content
300
+ }];
301
+ const parts = [];
302
+ const prefix = content.slice(0, unmatched.index);
303
+ const plainSuffix = content.slice(unmatched.index + unmatched.length);
304
+ if (prefix) parts.push({
305
+ type: "markdown",
306
+ content: prefix
307
+ });
308
+ if (plainSuffix) parts.push({
309
+ type: "plain",
310
+ content: plainSuffix
311
+ });
312
+ return parts;
313
+ }
314
+ //#endregion
200
315
  //#region src/components/Messages/Messages.tsx
201
316
  function getMessageColor(role) {
202
317
  switch (role) {
@@ -206,46 +321,92 @@ function getMessageColor(role) {
206
321
  default: return;
207
322
  }
208
323
  }
324
+ var FENCE_LINE_REGEX = /^(?<indent>[ \t]*)(?<fence>`{3,})(?<language>\w+)?[ \t]*$/;
325
+ function flushTextSegment(segments, textLines) {
326
+ const textContent = textLines.join("\n").trim();
327
+ if (textContent) segments.push({
328
+ type: "text",
329
+ content: textContent
330
+ });
331
+ }
332
+ function flushCodeSegment(segments, codeLines, fenceState) {
333
+ if (fenceState.ambiguous) {
334
+ segments.push({
335
+ type: "raw",
336
+ content: fenceState.rawLines.join("\n")
337
+ });
338
+ return;
339
+ }
340
+ const codeContent = normalizeCodeBlockContent(codeLines.join("\n"), fenceState.indent);
341
+ if (codeContent) segments.push({
342
+ type: "code",
343
+ content: codeContent,
344
+ language: fenceState.language
345
+ });
346
+ }
347
+ function unwrapRawMarkdownFence(content) {
348
+ if (!content.startsWith("```markdown\n") || !content.endsWith("\n```")) return null;
349
+ return content.slice(12, -4);
350
+ }
209
351
  function parseContent(content) {
210
352
  const segments = [];
211
- let lastIndex = 0;
212
- let match;
213
- CODE_BLOCK_REGEX.lastIndex = 0;
214
- while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
215
- if (match.index > lastIndex) {
216
- const textContent = content.slice(lastIndex, match.index).trim();
217
- // v8 ignore next 2 - Defensive check for empty trimmed content
218
- if (textContent) segments.push({
219
- type: "text",
220
- content: textContent
221
- });
353
+ const lines = content.split("\n");
354
+ const textLines = [];
355
+ const codeLines = [];
356
+ let fenceState = null;
357
+ for (const line of lines) {
358
+ const fenceMatch = FENCE_LINE_REGEX.exec(line);
359
+ if (fenceMatch?.groups) {
360
+ const { indent, fence, language } = fenceMatch.groups;
361
+ if (!fenceState) {
362
+ flushTextSegment(segments, textLines);
363
+ textLines.length = 0;
364
+ fenceState = {
365
+ indent,
366
+ fence,
367
+ language,
368
+ rawLines: [line],
369
+ ambiguous: false,
370
+ rawFenceDepth: 1
371
+ };
372
+ continue;
373
+ }
374
+ if (indent === fenceState.indent && fence === fenceState.fence) {
375
+ fenceState.rawLines.push(line);
376
+ if (fenceState.ambiguous) {
377
+ if (language) {
378
+ fenceState.rawFenceDepth += 1;
379
+ continue;
380
+ }
381
+ fenceState.rawFenceDepth -= 1;
382
+ if (fenceState.rawFenceDepth === 0) {
383
+ flushCodeSegment(segments, codeLines, fenceState);
384
+ codeLines.length = 0;
385
+ fenceState = null;
386
+ }
387
+ continue;
388
+ }
389
+ if (!language) {
390
+ flushCodeSegment(segments, codeLines, fenceState);
391
+ codeLines.length = 0;
392
+ fenceState = null;
393
+ continue;
394
+ }
395
+ fenceState.ambiguous = true;
396
+ fenceState.rawFenceDepth += 1;
397
+ continue;
398
+ }
222
399
  }
223
- const language = match[2];
224
- const codeContent = match[3].trim();
225
- // v8 ignore next 2 - Defensive check for empty code block
226
- if (codeContent) segments.push({
227
- type: "code",
228
- content: codeContent,
229
- language
230
- });
231
- lastIndex = match.index + match[0].length;
232
- }
233
- if (lastIndex < content.length) {
234
- const textContent = content.slice(lastIndex).trim();
235
- // v8 ignore next 2 - Defensive check for empty trimmed content
236
- if (textContent) segments.push({
237
- type: "text",
238
- content: textContent
239
- });
400
+ if (fenceState) {
401
+ fenceState.rawLines.push(line);
402
+ codeLines.push(line);
403
+ } else textLines.push(line);
240
404
  }
241
- // v8 ignore next 2 - Defensive fallback for edge case
242
- if (!segments.length && content.trim()) segments.push({
243
- type: "text",
244
- content: content.trim()
245
- });
405
+ if (fenceState) textLines.push(...fenceState.rawLines);
406
+ flushTextSegment(segments, textLines);
246
407
  return segments;
247
408
  }
248
- var Message = memo(function Message({ message }) {
409
+ var Message = memo(function Message({ message, isStreaming = false }) {
249
410
  const messageColor = getMessageColor(message.role);
250
411
  const isSystem = message.role === SYSTEM;
251
412
  const isUser = message.role === USER;
@@ -275,15 +436,34 @@ var Message = memo(function Message({ message }) {
275
436
  role: message.role
276
437
  })
277
438
  }, index);
439
+ if (segment.type === "raw") {
440
+ const markdownSource = unwrapRawMarkdownFence(segment.content);
441
+ return /* @__PURE__ */ jsx(Box, {
442
+ marginX: 2,
443
+ children: /* @__PURE__ */ jsx(CodeBlock, {
444
+ code: markdownSource ?? segment.content,
445
+ language: markdownSource ? "markdown" : segment.language,
446
+ role: message.role
447
+ })
448
+ }, index);
449
+ }
450
+ const textParts = isStreaming && !isUser ? splitStreamingInlineContent(segment.content) : [{
451
+ type: "markdown",
452
+ content: segment.content
453
+ }];
278
454
  return isUser ? /* @__PURE__ */ jsx(Text, {
279
455
  color: messageColor,
280
456
  children: prefix + segment.content
281
457
  }, index) : /* @__PURE__ */ jsx(Box, {
458
+ flexDirection: "column",
282
459
  marginX: 2,
283
- children: /* @__PURE__ */ jsx(Markdown, {
284
- content: segment.content,
460
+ children: textParts.map((part, partIndex) => part.type === "plain" ? /* @__PURE__ */ jsx(Text, {
461
+ color: messageColor,
462
+ children: part.content
463
+ }, partIndex) : /* @__PURE__ */ jsx(Markdown, {
464
+ content: part.content,
285
465
  color: messageColor
286
- })
466
+ }, partIndex))
287
467
  }, index);
288
468
  })
289
469
  });
@@ -296,7 +476,10 @@ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
296
476
  items: messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE),
297
477
  children: (message, index) => /* @__PURE__ */ jsx(Message, { message }, index)
298
478
  }, sessionId),
299
- streamingMessage && /* @__PURE__ */ jsx(Message, { message: streamingMessage }),
479
+ streamingMessage && /* @__PURE__ */ jsx(Message, {
480
+ isStreaming: true,
481
+ message: streamingMessage
482
+ }),
300
483
  isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
301
484
  marginTop: -1,
302
485
  marginBottom: 1,
@@ -1373,47 +1556,35 @@ function ModelPicker({ currentModel, onSelect, onClose }) {
1373
1556
  }
1374
1557
  //#endregion
1375
1558
  //#region src/components/SearchSettings.tsx
1376
- var View = /* @__PURE__ */ function(View) {
1377
- View["Menu"] = "menu";
1378
- View["Edit"] = "edit";
1379
- return View;
1380
- }(View || {});
1381
- var Action = /* @__PURE__ */ function(Action) {
1382
- Action["Set"] = "set";
1383
- Action["Clear"] = "clear";
1384
- Action["Cancel"] = "cancel";
1385
- return Action;
1386
- }(Action || {});
1387
1559
  function SearchSettings({ currentUrl, onClose, onSave }) {
1388
- const [view, setView] = useState(View.Menu);
1560
+ const [view, setView] = useState("menu");
1389
1561
  const [draftUrl, setDraftUrl] = useState(currentUrl ?? "");
1390
1562
  const [error, setError] = useState(null);
1391
1563
  const options = useMemo(() => {
1392
1564
  const nextOptions = [{
1393
1565
  label: currentUrl ? "Update SearXNG URL" : "Set SearXNG URL",
1394
- value: Action.Set
1566
+ value: "set"
1395
1567
  }];
1396
1568
  if (currentUrl) nextOptions.push({
1397
1569
  label: "Clear SearXNG URL",
1398
- value: Action.Clear
1570
+ value: "clear"
1399
1571
  });
1400
1572
  nextOptions.push({
1401
1573
  label: "Cancel",
1402
- value: Action.Cancel
1574
+ value: "cancel"
1403
1575
  });
1404
1576
  return nextOptions;
1405
1577
  }, [currentUrl]);
1406
1578
  const handleChange = useCallback((value) => {
1407
1579
  setError(null);
1408
1580
  switch (value) {
1409
- case Action.Set:
1581
+ case "set":
1410
1582
  setDraftUrl(currentUrl ?? "");
1411
- setView(View.Edit);
1583
+ setView("edit");
1412
1584
  break;
1413
- case Action.Clear:
1585
+ case "clear":
1414
1586
  onSave({ searxngBaseUrl: void 0 });
1415
1587
  break;
1416
- case Action.Cancel:
1417
1588
  default: onClose();
1418
1589
  }
1419
1590
  }, [
@@ -1439,13 +1610,13 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1439
1610
  }
1440
1611
  }, [onSave]);
1441
1612
  useInput((input, key) => {
1442
- if (view === View.Edit && (key.escape || key.ctrl && input === "c")) {
1613
+ if (view === "edit" && (key.escape || key.ctrl && input === "c")) {
1443
1614
  setDraftUrl(currentUrl ?? "");
1444
1615
  setError(null);
1445
- setView(View.Menu);
1616
+ setView("menu");
1446
1617
  }
1447
1618
  });
1448
- if (view === View.Edit) return /* @__PURE__ */ jsxs(Box, {
1619
+ if (view === "edit") return /* @__PURE__ */ jsxs(Box, {
1449
1620
  flexDirection: "column",
1450
1621
  children: [
1451
1622
  /* @__PURE__ */ jsx(Text, { children: "Set the SearXNG base URL. DuckDuckGo remains the fallback." }),
@@ -1482,11 +1653,6 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1482
1653
  }
1483
1654
  //#endregion
1484
1655
  //#region src/components/SessionManager.tsx
1485
- var VIEW = /* @__PURE__ */ function(VIEW) {
1486
- VIEW["MAIN"] = "main";
1487
- VIEW["DELETE"] = "delete";
1488
- return VIEW;
1489
- }(VIEW || {});
1490
1656
  var ACTION = {
1491
1657
  BACK: "back",
1492
1658
  CLOSE: "close",
@@ -1500,11 +1666,11 @@ function formatSessionLabel(session) {
1500
1666
  return `${session.title} (${timestamp})`;
1501
1667
  }
1502
1668
  function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen }) {
1503
- const [view, setView] = useState(VIEW.MAIN);
1669
+ const [view, setView] = useState("main");
1504
1670
  const [error, setError] = useState();
1505
1671
  const [, refreshSessionList] = useState(0);
1506
1672
  const sessions = listSessions();
1507
- const options = view === VIEW.DELETE ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
1673
+ const options = view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
1508
1674
  label: `Delete ${formatSessionLabel(session)}`,
1509
1675
  value: `${ACTION.DELETE_PREFIX}${session.id}`
1510
1676
  })), {
@@ -1537,10 +1703,10 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1537
1703
  onNew();
1538
1704
  break;
1539
1705
  case value === ACTION.DELETE_MENU:
1540
- setView(VIEW.DELETE);
1706
+ setView("delete");
1541
1707
  break;
1542
1708
  case value === ACTION.BACK:
1543
- setView(VIEW.MAIN);
1709
+ setView("main");
1544
1710
  break;
1545
1711
  case value.startsWith(ACTION.DELETE_PREFIX):
1546
1712
  try {
@@ -1570,7 +1736,7 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1570
1736
  flexDirection: "column",
1571
1737
  children: [
1572
1738
  /* @__PURE__ */ jsx(Text, { children: "Sessions" }),
1573
- /* @__PURE__ */ jsx(SelectPromptHint, { message: view === VIEW.DELETE ? "Delete session" : "Select session" }),
1739
+ /* @__PURE__ */ jsx(SelectPromptHint, { message: view === "delete" ? "Delete session" : "Select session" }),
1574
1740
  error && /* @__PURE__ */ jsx(Box, {
1575
1741
  marginBottom: 1,
1576
1742
  children: /* @__PURE__ */ jsx(Text, {
@@ -1588,20 +1754,13 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1588
1754
  }
1589
1755
  //#endregion
1590
1756
  //#region src/components/App.tsx
1591
- var SCREEN = /* @__PURE__ */ function(SCREEN) {
1592
- SCREEN["CHAT"] = "chat";
1593
- SCREEN["MODEL_PICKER"] = "model-picker";
1594
- SCREEN["SEARCH_SETTINGS"] = "search-settings";
1595
- SCREEN["SESSION_MANAGER"] = "session-manager";
1596
- return SCREEN;
1597
- }(SCREEN || {});
1598
1757
  function createSession(sessionId, model) {
1599
1758
  return sessionId ? loadSession(sessionId) : createSession$1(model);
1600
1759
  }
1601
1760
  function App({ sessionId }) {
1602
1761
  const { exit } = useApp();
1603
1762
  const [appConfig, setConfig] = useState(() => loadConfig());
1604
- const [currentScreen, setScreen] = useState(SCREEN.CHAT);
1763
+ const [currentScreen, setScreen] = useState("chat");
1605
1764
  const [mode, setMode] = useState(SAFE);
1606
1765
  const [activeSession, setSession] = useState(() => createSession(sessionId, loadConfig().model));
1607
1766
  const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
@@ -1627,17 +1786,17 @@ function App({ sessionId }) {
1627
1786
  const handleCreateSession = useCallback(() => {
1628
1787
  const nextSession = createSession$1(appConfig.model);
1629
1788
  setActiveSession(nextSession);
1630
- setScreen(SCREEN.CHAT);
1789
+ setScreen("chat");
1631
1790
  clear(nextSession.metadata.id);
1632
1791
  return nextSession;
1633
1792
  }, [appConfig.model, setActiveSession]);
1634
1793
  const handleOpenSession = useCallback((sessionId) => {
1635
1794
  if (sessionRef.current.metadata.id === sessionId) {
1636
- setScreen(SCREEN.CHAT);
1795
+ setScreen("chat");
1637
1796
  return;
1638
1797
  }
1639
1798
  setActiveSession(loadSession(sessionId));
1640
- setScreen(SCREEN.CHAT);
1799
+ setScreen("chat");
1641
1800
  clear(sessionId);
1642
1801
  }, [setActiveSession]);
1643
1802
  const handleDeleteSession = useCallback((sessionId) => {
@@ -1646,7 +1805,7 @@ function App({ sessionId }) {
1646
1805
  if (current.metadata.id !== sessionId) return current;
1647
1806
  return createSession$1(appConfig.model);
1648
1807
  });
1649
- setScreen(SCREEN.SESSION_MANAGER);
1808
+ setScreen("session-manager");
1650
1809
  }, [appConfig.model]);
1651
1810
  const handleMessagesChange = useCallback((messages) => {
1652
1811
  setSession((current) => {
@@ -1663,17 +1822,17 @@ function App({ sessionId }) {
1663
1822
  const handleCommand = useCallback((command) => {
1664
1823
  switch (command) {
1665
1824
  case "/session":
1666
- setScreen(SCREEN.SESSION_MANAGER);
1825
+ setScreen("session-manager");
1667
1826
  break;
1668
1827
  case "/model":
1669
- setScreen(SCREEN.MODEL_PICKER);
1828
+ setScreen("model-picker");
1670
1829
  break;
1671
1830
  case "/search":
1672
- setScreen(SCREEN.SEARCH_SETTINGS);
1831
+ setScreen("search-settings");
1673
1832
  break;
1674
1833
  case "/clear": {
1675
1834
  resetSystemMessage();
1676
- setScreen(SCREEN.CHAT);
1835
+ setScreen("chat");
1677
1836
  const nextSession = createSession$1(appConfig.model);
1678
1837
  setActiveSession(nextSession);
1679
1838
  clear(nextSession.metadata.id);
@@ -1699,10 +1858,10 @@ function App({ sessionId }) {
1699
1858
  ...current,
1700
1859
  metadata: updateSessionModel(current.metadata.id, newModel)
1701
1860
  }));
1702
- setScreen(SCREEN.CHAT);
1861
+ setScreen("chat");
1703
1862
  }, []);
1704
1863
  const handleClose = useCallback(() => {
1705
- setScreen(SCREEN.CHAT);
1864
+ setScreen("chat");
1706
1865
  }, []);
1707
1866
  const handleToggleMode = useCallback(() => {
1708
1867
  setMode((mode) => {
@@ -1716,21 +1875,21 @@ function App({ sessionId }) {
1716
1875
  }, []);
1717
1876
  let screenContent;
1718
1877
  switch (currentScreen) {
1719
- case SCREEN.MODEL_PICKER:
1878
+ case "model-picker":
1720
1879
  screenContent = /* @__PURE__ */ jsx(ModelPicker, {
1721
1880
  currentModel: appConfig.model,
1722
1881
  onSelect: handleUpdateConfig,
1723
1882
  onClose: handleClose
1724
1883
  });
1725
1884
  break;
1726
- case SCREEN.SEARCH_SETTINGS:
1885
+ case "search-settings":
1727
1886
  screenContent = /* @__PURE__ */ jsx(SearchSettings, {
1728
1887
  currentUrl: appConfig.searxngBaseUrl,
1729
1888
  onSave: handleUpdateConfig,
1730
1889
  onClose: handleClose
1731
1890
  });
1732
1891
  break;
1733
- case SCREEN.SESSION_MANAGER:
1892
+ case "session-manager":
1734
1893
  screenContent = /* @__PURE__ */ jsx(SessionManager, {
1735
1894
  currentSessionId: activeSession.metadata.id,
1736
1895
  onClose: handleClose,
@@ -1739,7 +1898,7 @@ function App({ sessionId }) {
1739
1898
  onOpen: handleOpenSession
1740
1899
  });
1741
1900
  break;
1742
- case SCREEN.CHAT:
1901
+ case "chat":
1743
1902
  screenContent = /* @__PURE__ */ jsx(Chat, {
1744
1903
  initialMessages: activeSession.messages,
1745
1904
  model: appConfig.model,
package/dist/cli.js CHANGED
@@ -1,11 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import { t as runShell } from "./assets/shell-CipXM_WI.js";
3
2
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
3
  import cac from "cac";
5
4
  import { homedir } from "node:os";
6
5
  import { join } from "node:path";
7
6
  import { Ollama } from "ollama";
8
7
  import { v7 } from "uuid";
8
+ import { exec } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ //#region \0rolldown/runtime.js
11
+ var __defProp = Object.defineProperty;
12
+ var __exportAll = (all, no_symbols) => {
13
+ let target = {};
14
+ for (var name in all) __defProp(target, name, {
15
+ get: all[name],
16
+ enumerable: true
17
+ });
18
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
19
+ return target;
20
+ };
21
+ //#endregion
9
22
  //#region src/constants/command.ts
10
23
  var LIST = [
11
24
  {
@@ -32,7 +45,7 @@ var LIST = [
32
45
  //#endregion
33
46
  //#region package.json
34
47
  var name = "code-ollama";
35
- var version = "0.14.1";
48
+ var version = "0.14.2";
36
49
  //#endregion
37
50
  //#region src/constants/package.ts
38
51
  var NAME = name;
@@ -603,7 +616,7 @@ function listDir(dirPath) {
603
616
  * Search for pattern in files using ripgrep if available, fallback to Node.js
604
617
  */
605
618
  async function grepSearch(pattern, dirPath) {
606
- const { execShell } = await import("./assets/shell-CipXM_WI.js").then((n) => n.n);
619
+ const { execShell } = await Promise.resolve().then(() => shell_exports);
607
620
  try {
608
621
  const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${pattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
609
622
  // v8 ignore next
@@ -642,6 +655,37 @@ async function grepSearch(pattern, dirPath) {
642
655
  }
643
656
  }
644
657
  //#endregion
658
+ //#region src/utils/tools/shell.ts
659
+ var shell_exports = /* @__PURE__ */ __exportAll({
660
+ execShell: () => execShell,
661
+ runShell: () => runShell
662
+ });
663
+ var execAsync = promisify(exec);
664
+ var SHELL_EXEC_OPTIONS = {
665
+ timeout: 3e4,
666
+ maxBuffer: 1024 * 1024
667
+ };
668
+ /**
669
+ * Execute shell command with shared options (throws on error)
670
+ */
671
+ function execShell(command) {
672
+ return execAsync(command, SHELL_EXEC_OPTIONS);
673
+ }
674
+ /**
675
+ * Execute shell command
676
+ */
677
+ async function runShell(command) {
678
+ try {
679
+ const { stdout, stderr } = await execShell(command);
680
+ return { content: stdout || stderr };
681
+ } catch (error) {
682
+ return {
683
+ content: "",
684
+ error: `Command failed: ${error instanceof Error ? error.message : String(error)}`
685
+ };
686
+ }
687
+ }
688
+ //#endregion
645
689
  //#region src/utils/tools/web/fetch.ts
646
690
  var FETCH_TIMEOUT_MS = 1e4;
647
691
  var BASE_HEADERS = { "user-agent": `${NAME}/${VERSION}` };
@@ -904,7 +948,7 @@ async function main(args = process.argv.slice(2)) {
904
948
  else await launchTui();
905
949
  }
906
950
  async function launchTui(sessionId) {
907
- const { renderApp } = await import("./assets/tui-CoX71F7Y.js");
951
+ const { renderApp } = await import("./assets/tui-Dse5XVJ_.js");
908
952
  reset();
909
953
  renderApp(sessionId);
910
954
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
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",
@@ -54,7 +54,7 @@
54
54
  "@commitlint/config-conventional": "21.0.1",
55
55
  "@eslint/config-helpers": "0.6.0",
56
56
  "@eslint/js": "10.0.1",
57
- "@types/node": "25.7.0",
57
+ "@types/node": "25.8.0",
58
58
  "@types/react": "19.2.14",
59
59
  "@vitest/coverage-v8": "4.1.6",
60
60
  "eslint": "10.3.0",
@@ -66,10 +66,10 @@
66
66
  "lint-staged": "17.0.4",
67
67
  "prettier": "3.8.3",
68
68
  "publint": "0.3.21",
69
- "tsx": "4.21.0",
69
+ "tsx": "4.22.0",
70
70
  "typescript": "6.0.3",
71
71
  "typescript-eslint": "8.59.3",
72
- "vite": "8.0.12",
72
+ "vite": "8.0.13",
73
73
  "vitest": "4.1.6"
74
74
  },
75
75
  "files": [
@@ -1,46 +0,0 @@
1
- import { exec } from "node:child_process";
2
- import { promisify } from "node:util";
3
- //#region \0rolldown/runtime.js
4
- var __defProp = Object.defineProperty;
5
- var __exportAll = (all, no_symbols) => {
6
- let target = {};
7
- for (var name in all) __defProp(target, name, {
8
- get: all[name],
9
- enumerable: true
10
- });
11
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
12
- return target;
13
- };
14
- //#endregion
15
- //#region src/utils/tools/shell.ts
16
- var shell_exports = /* @__PURE__ */ __exportAll({
17
- execShell: () => execShell,
18
- runShell: () => runShell
19
- });
20
- var execAsync = promisify(exec);
21
- var SHELL_EXEC_OPTIONS = {
22
- timeout: 3e4,
23
- maxBuffer: 1024 * 1024
24
- };
25
- /**
26
- * Execute shell command with shared options (throws on error)
27
- */
28
- function execShell(command) {
29
- return execAsync(command, SHELL_EXEC_OPTIONS);
30
- }
31
- /**
32
- * Execute shell command
33
- */
34
- async function runShell(command) {
35
- try {
36
- const { stdout, stderr } = await execShell(command);
37
- return { content: stdout || stderr };
38
- } catch (error) {
39
- return {
40
- content: "",
41
- error: `Command failed: ${error instanceof Error ? error.message : String(error)}`
42
- };
43
- }
44
- }
45
- //#endregion
46
- export { shell_exports as n, runShell as t };