code-ollama 0.14.0 → 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
  }
@@ -65,7 +71,7 @@ var CodeBlock = memo(function CodeBlock({ code, language, role }) {
65
71
  const isSystem = role === SYSTEM;
66
72
  return /* @__PURE__ */ jsx(Box, {
67
73
  flexDirection: "column",
68
- borderStyle: "round",
74
+ borderStyle: "bold",
69
75
  borderColor: isSystem ? "gray" : "dim",
70
76
  paddingX: 1,
71
77
  marginY: 1,
@@ -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,
@@ -308,11 +491,12 @@ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
308
491
  }
309
492
  //#endregion
310
493
  //#region src/components/SelectPrompt.tsx
311
- function SelectPrompt({ children, onCancel, ...selectProps }) {
494
+ function SelectPrompt({ borderStyle, children, onCancel, ...selectProps }) {
312
495
  useInput((input, key) => {
313
496
  if (key.escape || key.ctrl && input === "c") onCancel?.();
314
497
  });
315
498
  return /* @__PURE__ */ jsxs(Box, {
499
+ borderStyle,
316
500
  flexDirection: "column",
317
501
  children: [children, /* @__PURE__ */ jsx(Select, { ...selectProps })]
318
502
  });
@@ -373,29 +557,32 @@ var options$1 = [
373
557
  }
374
558
  ];
375
559
  function PlanApproval({ planContent, onModeChange }) {
376
- return /* @__PURE__ */ jsx(SelectPrompt, {
377
- options: options$1,
378
- onChange: useCallback((value) => {
379
- onModeChange(value);
380
- }, [onModeChange]),
381
- onCancel: useCallback(() => {
382
- onModeChange(PLAN);
383
- }, [onModeChange]),
384
- children: /* @__PURE__ */ jsxs(Box, {
385
- flexDirection: "column",
386
- marginTop: 1,
387
- children: [
388
- /* @__PURE__ */ jsx(Text, {
389
- bold: true,
390
- color: "magenta",
391
- children: "Plan Generated - Choose execution mode:"
392
- }),
393
- /* @__PURE__ */ jsx(Box, {
394
- marginY: 1,
395
- children: /* @__PURE__ */ jsx(Text, { children: planContent })
396
- }),
397
- /* @__PURE__ */ jsx(SelectPromptHint, { message: "Select execution mode" })
398
- ]
560
+ return /* @__PURE__ */ jsx(Box, {
561
+ marginX: 2,
562
+ children: /* @__PURE__ */ jsx(SelectPrompt, {
563
+ borderStyle: "bold",
564
+ options: options$1,
565
+ onChange: useCallback((value) => {
566
+ onModeChange(value);
567
+ }, [onModeChange]),
568
+ onCancel: useCallback(() => {
569
+ onModeChange(PLAN);
570
+ }, [onModeChange]),
571
+ children: /* @__PURE__ */ jsxs(Box, {
572
+ flexDirection: "column",
573
+ children: [
574
+ /* @__PURE__ */ jsx(Text, {
575
+ bold: true,
576
+ color: "magenta",
577
+ children: "Plan Generated - Choose execution mode:"
578
+ }),
579
+ /* @__PURE__ */ jsx(Box, {
580
+ marginY: 1,
581
+ children: /* @__PURE__ */ jsx(Text, { children: planContent })
582
+ }),
583
+ /* @__PURE__ */ jsx(SelectPromptHint, { message: "Select execution mode" })
584
+ ]
585
+ })
399
586
  })
400
587
  });
401
588
  }
@@ -416,47 +603,51 @@ function ToolApproval({ toolCall, onDecision }) {
416
603
  onDecision(REJECT);
417
604
  }, [onDecision]);
418
605
  const args = JSON.stringify(toolCall.function.arguments, null, 2);
419
- return /* @__PURE__ */ jsxs(SelectPrompt, {
420
- options,
421
- onChange: handleChange,
422
- onCancel: handleEscape,
423
- children: [
424
- /* @__PURE__ */ jsx(Text, {
425
- color: "yellow",
426
- children: "⚠️ Tool requires approval:"
427
- }),
428
- /* @__PURE__ */ jsxs(Box, {
429
- marginX: 3,
430
- marginBottom: 1,
431
- flexDirection: "column",
432
- children: [/* @__PURE__ */ jsxs(Text, { children: [
433
- /* @__PURE__ */ jsx(Text, {
434
- italic: true,
435
- children: "Tool:"
436
- }),
437
- " ",
438
- toolCall.function.name
439
- ] }), /* @__PURE__ */ jsxs(Text, { children: [
440
- /* @__PURE__ */ jsx(Text, {
441
- italic: true,
442
- children: "Arguments:"
443
- }),
444
- " ",
445
- args
446
- ] })]
447
- }),
448
- /* @__PURE__ */ jsx(SelectPromptHint, {
449
- message: "Select approval action",
450
- escapeLabel: "reject"
451
- })
452
- ]
606
+ return /* @__PURE__ */ jsx(Box, {
607
+ marginX: 2,
608
+ children: /* @__PURE__ */ jsxs(SelectPrompt, {
609
+ borderStyle: "bold",
610
+ options,
611
+ onChange: handleChange,
612
+ onCancel: handleEscape,
613
+ children: [
614
+ /* @__PURE__ */ jsx(Text, {
615
+ color: "yellow",
616
+ children: "Tool requires approval ⚠️ "
617
+ }),
618
+ /* @__PURE__ */ jsxs(Box, {
619
+ flexDirection: "column",
620
+ marginBottom: 1,
621
+ marginX: 2,
622
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
623
+ /* @__PURE__ */ jsx(Text, {
624
+ dimColor: true,
625
+ children: "Tool:"
626
+ }),
627
+ " ",
628
+ toolCall.function.name
629
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [
630
+ /* @__PURE__ */ jsx(Text, {
631
+ dimColor: true,
632
+ children: "Arguments:"
633
+ }),
634
+ " ",
635
+ args
636
+ ] })]
637
+ }),
638
+ /* @__PURE__ */ jsx(SelectPromptHint, {
639
+ message: "Select approval action",
640
+ escapeLabel: "reject"
641
+ })
642
+ ]
643
+ })
453
644
  });
454
645
  }
455
646
  //#endregion
456
647
  //#region src/components/Chat/constants.ts
457
648
  var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
458
649
  var PLAN_CHECKLIST_REMINDER = "Then display the execution plan as an unchecked Markdown checklist only";
459
- var PLAN_EXECUTION_REMINDER = "Do not claim success and do not call write_file or run_shell until the user approves execution";
650
+ var PLAN_EXECUTION_REMINDER = `Do not claim success and do not call ${Array.from(WRITE_TOOLS).join(", ")} until the user approves execution`;
460
651
  var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
461
652
  INTERRUPT_REASON["INTERRUPTED"] = "interrupted";
462
653
  INTERRUPT_REASON["REJECTED"] = "rejected";
@@ -464,7 +655,39 @@ var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
464
655
  }({});
465
656
  //#endregion
466
657
  //#region src/components/TextInput/TextInput.tsx
467
- function TextInput({ value, isDisabled = false, placeholder, cursorPosition: externalCursorPosition, onChange, onSubmit }) {
658
+ function buildLineSegments(displayValue, cursorPosition, width) {
659
+ const safeWidth = Math.max(1, width);
660
+ const cursorChar = displayValue[cursorPosition] || " ";
661
+ const renderValue = displayValue.slice(0, cursorPosition) + cursorChar + displayValue.slice(cursorPosition + 1);
662
+ const totalLength = Math.max(1, renderValue.length);
663
+ const lines = [];
664
+ for (let start = 0; start < totalLength; start += safeWidth) {
665
+ const end = start + safeWidth;
666
+ const text = renderValue.slice(start, end);
667
+ const hasCursor = cursorPosition >= start && cursorPosition < end;
668
+ if (!hasCursor) {
669
+ lines.push({
670
+ text,
671
+ hasCursor,
672
+ beforeCursor: "",
673
+ cursorChar: " ",
674
+ afterCursor: ""
675
+ });
676
+ continue;
677
+ }
678
+ const offset = cursorPosition - start;
679
+ lines.push({
680
+ text,
681
+ hasCursor,
682
+ beforeCursor: text.slice(0, offset),
683
+ cursorChar: text[offset] || " ",
684
+ afterCursor: text.slice(offset + 1)
685
+ });
686
+ }
687
+ return lines;
688
+ }
689
+ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: externalCursorPosition, wrapIndent = 0, onChange, onSubmit }) {
690
+ const { stdout } = useStdout();
468
691
  const [cursorPosition, setCursorPosition] = useState(value.length);
469
692
  const prevValueRef = useRef(value);
470
693
  const prevExternalCursorRef = useRef(externalCursorPosition);
@@ -522,6 +745,14 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
522
745
  setCursorPosition(value.length);
523
746
  return;
524
747
  }
748
+ if (key.ctrl && input === "a") {
749
+ setCursorPosition(0);
750
+ return;
751
+ }
752
+ if (key.ctrl && input === "e") {
753
+ setCursorPosition(value.length);
754
+ return;
755
+ }
525
756
  // v8 ignore start
526
757
  if (input) {
527
758
  onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
@@ -531,23 +762,31 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
531
762
  }, { isActive: !isDisabled });
532
763
  const displayValue = value || (placeholder ?? "");
533
764
  const isPlaceholder = Boolean(!value && placeholder);
534
- const cursorChar = displayValue[cursorPosition] || " ";
535
- const before = displayValue.slice(0, cursorPosition);
536
- const after = displayValue.slice(cursorPosition + 1);
537
- return /* @__PURE__ */ jsxs(Fragment, { children: [
538
- /* @__PURE__ */ jsx(Text, {
539
- dimColor: isPlaceholder,
540
- children: before
541
- }),
542
- /* @__PURE__ */ jsx(Text, {
543
- inverse: true,
544
- children: cursorChar
545
- }),
546
- /* @__PURE__ */ jsx(Text, {
765
+ const availableWidth = Math.max(1, stdout.columns - wrapIndent);
766
+ return /* @__PURE__ */ jsx(Box, {
767
+ flexDirection: "column",
768
+ children: useMemo(() => buildLineSegments(displayValue, cursorPosition, availableWidth), [
769
+ availableWidth,
770
+ cursorPosition,
771
+ displayValue
772
+ ]).map((line, index) => /* @__PURE__ */ jsx(Text, { children: line.hasCursor ? /* @__PURE__ */ jsxs(Fragment, { children: [
773
+ /* @__PURE__ */ jsx(Text, {
774
+ dimColor: isPlaceholder,
775
+ children: line.beforeCursor
776
+ }),
777
+ /* @__PURE__ */ jsx(Text, {
778
+ inverse: true,
779
+ children: line.cursorChar
780
+ }),
781
+ /* @__PURE__ */ jsx(Text, {
782
+ dimColor: isPlaceholder,
783
+ children: line.afterCursor
784
+ })
785
+ ] }) : /* @__PURE__ */ jsx(Text, {
547
786
  dimColor: isPlaceholder,
548
- children: after
549
- })
550
- ] });
787
+ children: line.text
788
+ }) }, `${String(index)}-${line.text}`))
789
+ });
551
790
  }
552
791
  //#endregion
553
792
  //#region src/components/Chat/CommandMenu.tsx
@@ -789,6 +1028,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
789
1028
  value: input,
790
1029
  isDisabled,
791
1030
  cursorPosition,
1031
+ wrapIndent: 2,
792
1032
  onChange: setInput,
793
1033
  onSubmit: handleSubmitText,
794
1034
  placeholder: "Ask anything... (/ commands, @ files)"
@@ -1228,7 +1468,7 @@ function Header({ model, onLoad }) {
1228
1468
  return /* @__PURE__ */ jsx(Static, {
1229
1469
  items: [0],
1230
1470
  children: (key) => /* @__PURE__ */ jsxs(Box, {
1231
- borderStyle: "round",
1471
+ borderStyle: "bold",
1232
1472
  flexDirection: "column",
1233
1473
  paddingX: 1,
1234
1474
  children: [
@@ -1316,47 +1556,35 @@ function ModelPicker({ currentModel, onSelect, onClose }) {
1316
1556
  }
1317
1557
  //#endregion
1318
1558
  //#region src/components/SearchSettings.tsx
1319
- var View = /* @__PURE__ */ function(View) {
1320
- View["Menu"] = "menu";
1321
- View["Edit"] = "edit";
1322
- return View;
1323
- }(View || {});
1324
- var Action = /* @__PURE__ */ function(Action) {
1325
- Action["Set"] = "set";
1326
- Action["Clear"] = "clear";
1327
- Action["Cancel"] = "cancel";
1328
- return Action;
1329
- }(Action || {});
1330
1559
  function SearchSettings({ currentUrl, onClose, onSave }) {
1331
- const [view, setView] = useState(View.Menu);
1560
+ const [view, setView] = useState("menu");
1332
1561
  const [draftUrl, setDraftUrl] = useState(currentUrl ?? "");
1333
1562
  const [error, setError] = useState(null);
1334
1563
  const options = useMemo(() => {
1335
1564
  const nextOptions = [{
1336
1565
  label: currentUrl ? "Update SearXNG URL" : "Set SearXNG URL",
1337
- value: Action.Set
1566
+ value: "set"
1338
1567
  }];
1339
1568
  if (currentUrl) nextOptions.push({
1340
1569
  label: "Clear SearXNG URL",
1341
- value: Action.Clear
1570
+ value: "clear"
1342
1571
  });
1343
1572
  nextOptions.push({
1344
1573
  label: "Cancel",
1345
- value: Action.Cancel
1574
+ value: "cancel"
1346
1575
  });
1347
1576
  return nextOptions;
1348
1577
  }, [currentUrl]);
1349
1578
  const handleChange = useCallback((value) => {
1350
1579
  setError(null);
1351
1580
  switch (value) {
1352
- case Action.Set:
1581
+ case "set":
1353
1582
  setDraftUrl(currentUrl ?? "");
1354
- setView(View.Edit);
1583
+ setView("edit");
1355
1584
  break;
1356
- case Action.Clear:
1585
+ case "clear":
1357
1586
  onSave({ searxngBaseUrl: void 0 });
1358
1587
  break;
1359
- case Action.Cancel:
1360
1588
  default: onClose();
1361
1589
  }
1362
1590
  }, [
@@ -1382,18 +1610,19 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1382
1610
  }
1383
1611
  }, [onSave]);
1384
1612
  useInput((input, key) => {
1385
- if (view === View.Edit && (key.escape || key.ctrl && input === "c")) {
1613
+ if (view === "edit" && (key.escape || key.ctrl && input === "c")) {
1386
1614
  setDraftUrl(currentUrl ?? "");
1387
1615
  setError(null);
1388
- setView(View.Menu);
1616
+ setView("menu");
1389
1617
  }
1390
1618
  });
1391
- if (view === View.Edit) return /* @__PURE__ */ jsxs(Box, {
1619
+ if (view === "edit") return /* @__PURE__ */ jsxs(Box, {
1392
1620
  flexDirection: "column",
1393
1621
  children: [
1394
1622
  /* @__PURE__ */ jsx(Text, { children: "Set the SearXNG base URL. DuckDuckGo remains the fallback." }),
1395
1623
  /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
1396
1624
  value: draftUrl,
1625
+ wrapIndent: 2,
1397
1626
  onChange: setDraftUrl,
1398
1627
  onSubmit: handleSubmit,
1399
1628
  placeholder: "http://localhost:8080"
@@ -1424,11 +1653,6 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
1424
1653
  }
1425
1654
  //#endregion
1426
1655
  //#region src/components/SessionManager.tsx
1427
- var VIEW = /* @__PURE__ */ function(VIEW) {
1428
- VIEW["MAIN"] = "main";
1429
- VIEW["DELETE"] = "delete";
1430
- return VIEW;
1431
- }(VIEW || {});
1432
1656
  var ACTION = {
1433
1657
  BACK: "back",
1434
1658
  CLOSE: "close",
@@ -1442,11 +1666,11 @@ function formatSessionLabel(session) {
1442
1666
  return `${session.title} (${timestamp})`;
1443
1667
  }
1444
1668
  function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen }) {
1445
- const [view, setView] = useState(VIEW.MAIN);
1669
+ const [view, setView] = useState("main");
1446
1670
  const [error, setError] = useState();
1447
1671
  const [, refreshSessionList] = useState(0);
1448
1672
  const sessions = listSessions();
1449
- const options = view === VIEW.DELETE ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
1673
+ const options = view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
1450
1674
  label: `Delete ${formatSessionLabel(session)}`,
1451
1675
  value: `${ACTION.DELETE_PREFIX}${session.id}`
1452
1676
  })), {
@@ -1479,10 +1703,10 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1479
1703
  onNew();
1480
1704
  break;
1481
1705
  case value === ACTION.DELETE_MENU:
1482
- setView(VIEW.DELETE);
1706
+ setView("delete");
1483
1707
  break;
1484
1708
  case value === ACTION.BACK:
1485
- setView(VIEW.MAIN);
1709
+ setView("main");
1486
1710
  break;
1487
1711
  case value.startsWith(ACTION.DELETE_PREFIX):
1488
1712
  try {
@@ -1512,7 +1736,7 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1512
1736
  flexDirection: "column",
1513
1737
  children: [
1514
1738
  /* @__PURE__ */ jsx(Text, { children: "Sessions" }),
1515
- /* @__PURE__ */ jsx(SelectPromptHint, { message: view === VIEW.DELETE ? "Delete session" : "Select session" }),
1739
+ /* @__PURE__ */ jsx(SelectPromptHint, { message: view === "delete" ? "Delete session" : "Select session" }),
1516
1740
  error && /* @__PURE__ */ jsx(Box, {
1517
1741
  marginBottom: 1,
1518
1742
  children: /* @__PURE__ */ jsx(Text, {
@@ -1530,20 +1754,13 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
1530
1754
  }
1531
1755
  //#endregion
1532
1756
  //#region src/components/App.tsx
1533
- var SCREEN = /* @__PURE__ */ function(SCREEN) {
1534
- SCREEN["CHAT"] = "chat";
1535
- SCREEN["MODEL_PICKER"] = "model-picker";
1536
- SCREEN["SEARCH_SETTINGS"] = "search-settings";
1537
- SCREEN["SESSION_MANAGER"] = "session-manager";
1538
- return SCREEN;
1539
- }(SCREEN || {});
1540
1757
  function createSession(sessionId, model) {
1541
1758
  return sessionId ? loadSession(sessionId) : createSession$1(model);
1542
1759
  }
1543
1760
  function App({ sessionId }) {
1544
1761
  const { exit } = useApp();
1545
1762
  const [appConfig, setConfig] = useState(() => loadConfig());
1546
- const [currentScreen, setScreen] = useState(SCREEN.CHAT);
1763
+ const [currentScreen, setScreen] = useState("chat");
1547
1764
  const [mode, setMode] = useState(SAFE);
1548
1765
  const [activeSession, setSession] = useState(() => createSession(sessionId, loadConfig().model));
1549
1766
  const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
@@ -1569,17 +1786,17 @@ function App({ sessionId }) {
1569
1786
  const handleCreateSession = useCallback(() => {
1570
1787
  const nextSession = createSession$1(appConfig.model);
1571
1788
  setActiveSession(nextSession);
1572
- setScreen(SCREEN.CHAT);
1789
+ setScreen("chat");
1573
1790
  clear(nextSession.metadata.id);
1574
1791
  return nextSession;
1575
1792
  }, [appConfig.model, setActiveSession]);
1576
1793
  const handleOpenSession = useCallback((sessionId) => {
1577
1794
  if (sessionRef.current.metadata.id === sessionId) {
1578
- setScreen(SCREEN.CHAT);
1795
+ setScreen("chat");
1579
1796
  return;
1580
1797
  }
1581
1798
  setActiveSession(loadSession(sessionId));
1582
- setScreen(SCREEN.CHAT);
1799
+ setScreen("chat");
1583
1800
  clear(sessionId);
1584
1801
  }, [setActiveSession]);
1585
1802
  const handleDeleteSession = useCallback((sessionId) => {
@@ -1588,7 +1805,7 @@ function App({ sessionId }) {
1588
1805
  if (current.metadata.id !== sessionId) return current;
1589
1806
  return createSession$1(appConfig.model);
1590
1807
  });
1591
- setScreen(SCREEN.SESSION_MANAGER);
1808
+ setScreen("session-manager");
1592
1809
  }, [appConfig.model]);
1593
1810
  const handleMessagesChange = useCallback((messages) => {
1594
1811
  setSession((current) => {
@@ -1605,17 +1822,17 @@ function App({ sessionId }) {
1605
1822
  const handleCommand = useCallback((command) => {
1606
1823
  switch (command) {
1607
1824
  case "/session":
1608
- setScreen(SCREEN.SESSION_MANAGER);
1825
+ setScreen("session-manager");
1609
1826
  break;
1610
1827
  case "/model":
1611
- setScreen(SCREEN.MODEL_PICKER);
1828
+ setScreen("model-picker");
1612
1829
  break;
1613
1830
  case "/search":
1614
- setScreen(SCREEN.SEARCH_SETTINGS);
1831
+ setScreen("search-settings");
1615
1832
  break;
1616
1833
  case "/clear": {
1617
1834
  resetSystemMessage();
1618
- setScreen(SCREEN.CHAT);
1835
+ setScreen("chat");
1619
1836
  const nextSession = createSession$1(appConfig.model);
1620
1837
  setActiveSession(nextSession);
1621
1838
  clear(nextSession.metadata.id);
@@ -1641,10 +1858,10 @@ function App({ sessionId }) {
1641
1858
  ...current,
1642
1859
  metadata: updateSessionModel(current.metadata.id, newModel)
1643
1860
  }));
1644
- setScreen(SCREEN.CHAT);
1861
+ setScreen("chat");
1645
1862
  }, []);
1646
1863
  const handleClose = useCallback(() => {
1647
- setScreen(SCREEN.CHAT);
1864
+ setScreen("chat");
1648
1865
  }, []);
1649
1866
  const handleToggleMode = useCallback(() => {
1650
1867
  setMode((mode) => {
@@ -1658,21 +1875,21 @@ function App({ sessionId }) {
1658
1875
  }, []);
1659
1876
  let screenContent;
1660
1877
  switch (currentScreen) {
1661
- case SCREEN.MODEL_PICKER:
1878
+ case "model-picker":
1662
1879
  screenContent = /* @__PURE__ */ jsx(ModelPicker, {
1663
1880
  currentModel: appConfig.model,
1664
1881
  onSelect: handleUpdateConfig,
1665
1882
  onClose: handleClose
1666
1883
  });
1667
1884
  break;
1668
- case SCREEN.SEARCH_SETTINGS:
1885
+ case "search-settings":
1669
1886
  screenContent = /* @__PURE__ */ jsx(SearchSettings, {
1670
1887
  currentUrl: appConfig.searxngBaseUrl,
1671
1888
  onSave: handleUpdateConfig,
1672
1889
  onClose: handleClose
1673
1890
  });
1674
1891
  break;
1675
- case SCREEN.SESSION_MANAGER:
1892
+ case "session-manager":
1676
1893
  screenContent = /* @__PURE__ */ jsx(SessionManager, {
1677
1894
  currentSessionId: activeSession.metadata.id,
1678
1895
  onClose: handleClose,
@@ -1681,7 +1898,7 @@ function App({ sessionId }) {
1681
1898
  onOpen: handleOpenSession
1682
1899
  });
1683
1900
  break;
1684
- case SCREEN.CHAT:
1901
+ case "chat":
1685
1902
  screenContent = /* @__PURE__ */ jsx(Chat, {
1686
1903
  initialMessages: activeSession.messages,
1687
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.0";
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-TX7C0xYg.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.0",
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",
@@ -42,7 +42,7 @@
42
42
  "@inkjs/ui": "2.0.0",
43
43
  "@shikijs/cli": "4.0.2",
44
44
  "cac": "7.0.0",
45
- "ink": "7.0.2",
45
+ "ink": "7.0.3",
46
46
  "marked": "15.0.12",
47
47
  "marked-terminal": "7.3.0",
48
48
  "ollama": "0.6.3",
@@ -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 };