opencode-miniterm 1.0.3 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-miniterm",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A small front-end terminal UI for OpenCode",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -25,13 +25,9 @@ function run(client: OpencodeClient, state: State): void {
25
25
  if (!part || !part.text.trim()) continue;
26
26
 
27
27
  if (part.title === "thinking") {
28
- pages.push(
29
- `${ansi.BOLD_BRIGHT_BLACK}~${ansi.RESET} ${ansi.BRIGHT_BLACK}${part.text.trimStart()}${ansi.RESET}`,
30
- );
28
+ pages.push(`šŸ’­ ${ansi.BRIGHT_BLACK}${part.text.trimStart()}${ansi.RESET}`);
31
29
  } else if (part.title === "response") {
32
- pages.push(
33
- `${ansi.WHITE_BACKGROUND}${ansi.BOLD_BLACK}*${ansi.RESET} ${part.text.trimStart()}`,
34
- );
30
+ pages.push(`šŸ’­ ${part.text.trimStart()}`);
35
31
  } else {
36
32
  pages.push(part.text);
37
33
  }
package/src/render.ts CHANGED
@@ -23,6 +23,7 @@ export function render(state: State, details = false): void {
23
23
 
24
24
  if (part.title === "thinking") {
25
25
  if (part.active === false) {
26
+ // We've already checked all the parts before here
26
27
  break;
27
28
  }
28
29
  part.active = !foundPart;
@@ -35,6 +36,7 @@ export function render(state: State, details = false): void {
35
36
  }
36
37
  }
37
38
 
39
+ let lastPartWasTool = false;
38
40
  for (let i = 0; i < state.accumulatedResponse.length; i++) {
39
41
  const part = state.accumulatedResponse[i];
40
42
  if (!part || !part.active) continue;
@@ -43,19 +45,29 @@ export function render(state: State, details = false): void {
43
45
  if (part.title === "thinking") {
44
46
  // Show max 10 thinking lines
45
47
  const partText = details ? part.text.trimStart() : lastThinkingLines(part.text.trimStart());
46
- output += `${ansi.BOLD_BRIGHT_BLACK}~${ansi.RESET} ${ansi.BRIGHT_BLACK}${partText}${ansi.RESET}\n\n`;
48
+ output += "<ocmt-thinking>\n";
49
+ output += `šŸ’­ ${partText}\n\n`;
50
+ output += "</ocmt-thinking>\n";
47
51
  } else if (part.title === "response") {
48
52
  // Show all response lines
49
53
  const doc = parse(part.text.trimStart(), gfm);
50
54
  const partText = renderToConsole(doc);
51
- output += `${ansi.WHITE_BACKGROUND}${ansi.BOLD_BLACK}*${ansi.RESET} ${partText}\n\n`;
52
- } else if (part.title === "tool" || part.title === "files") {
55
+ output += `šŸ’¬ ${partText}\n\n`;
56
+ } else if (part.title === "tool") {
57
+ // TODO: Show max 10 tool/file lines?
58
+ if (lastPartWasTool && output.endsWith("\n\n")) {
59
+ output = output.substring(0, output.length - 1);
60
+ }
61
+ output += part.text + "\n\n";
62
+ } else if (part.title === "files") {
53
63
  // TODO: Show max 10 tool/file lines?
54
64
  output += part.text + "\n\n";
55
65
  } else if (part.title === "todo") {
56
66
  // Show the whole todo list
57
67
  output += part.text + "\n\n";
58
68
  }
69
+
70
+ lastPartWasTool = part.title === "tool";
59
71
  }
60
72
 
61
73
  if (output) {
@@ -135,8 +147,18 @@ export function wrapText(text: string, width: number): string[] {
135
147
  let visibleLength = indentLength;
136
148
  let i = 0;
137
149
 
150
+ let inThinking = false;
151
+
138
152
  const pushLine = () => {
139
- lines.push(currentLine);
153
+ if (currentLine === " <ocmt-thinking>") {
154
+ inThinking = true;
155
+ } else if (currentLine === " </ocmt-thinking>") {
156
+ inThinking = false;
157
+ } else {
158
+ let text = inThinking ? `${ansi.BRIGHT_BLACK}${currentLine}${ansi.RESET}` : currentLine;
159
+ lines.push(text);
160
+ }
161
+
140
162
  currentLine = INDENT;
141
163
  visibleLength = indentLength;
142
164
  };
@@ -1,3 +1,4 @@
1
+ import { stripANSI } from "bun";
1
2
  import { describe, expect, it, vi } from "bun:test";
2
3
  import { type State } from "../src";
3
4
  import * as ansi from "../src/ansi";
@@ -62,7 +63,7 @@ describe("render", () => {
62
63
  const calls = write.mock.calls.map((c) => c[0]);
63
64
  expect(calls.some((c) => c.includes("\u001B[2A"))).toBe(true);
64
65
  const outputCall = calls.find((c) => c.includes("i've done it"));
65
- expect(outputCall).toContain(`${ansi.WHITE_BACKGROUND}${ansi.BOLD_BLACK}*${ansi.RESET}`);
66
+ expect(outputCall).toContain(`šŸ’¬`);
66
67
  });
67
68
  });
68
69
 
@@ -77,9 +78,7 @@ describe("render", () => {
77
78
  render(state);
78
79
 
79
80
  const output = write.mock.calls.map((c) => c[0]).join("");
80
- expect(output).toContain(
81
- `${ansi.BOLD_BRIGHT_BLACK}~${ansi.RESET} ${ansi.BRIGHT_BLACK}åˆ†ęžé—®é¢˜${ansi.RESET}`,
82
- );
81
+ expect(output).toContain(`${ansi.BRIGHT_BLACK} šŸ’­ åˆ†ęžé—®é¢˜${ansi.RESET}`);
83
82
  });
84
83
 
85
84
  it("should only show thinking indicator for last thinking part", () => {
@@ -95,9 +94,7 @@ describe("render", () => {
95
94
  render(state);
96
95
 
97
96
  const output = write.mock.calls.map((c) => c[0]).join("");
98
- expect(output).toContain(
99
- `${ansi.BOLD_BRIGHT_BLACK}~${ansi.RESET} ${ansi.BRIGHT_BLACK}second${ansi.RESET}`,
100
- );
97
+ expect(output).toContain(`${ansi.BRIGHT_BLACK} šŸ’­ second${ansi.RESET}`);
101
98
  expect(output).not.toContain("first");
102
99
  });
103
100
 
@@ -124,6 +121,31 @@ describe("render", () => {
124
121
 
125
122
  expect(write).toHaveBeenCalled();
126
123
  });
124
+
125
+ it("all thinking lines should be gray", () => {
126
+ const write = vi.fn();
127
+ const state = createMockState({
128
+ accumulatedResponse: [
129
+ {
130
+ key: "xxx",
131
+ title: "thinking",
132
+ text: "Cookware and bakeware is food preparation equipment, such as cooking pots, pans, baking sheets etc. used in kitchens. Cookware is used on a stove or range cooktop, while bakeware is used in an oven. Some utensils are considered both cookware and bakeware.",
133
+ },
134
+ ],
135
+ write,
136
+ });
137
+
138
+ render(state);
139
+
140
+ const output = write.mock.calls.map((c) => c[0]).join("");
141
+ expect(output).toContain(
142
+ `${ansi.BRIGHT_BLACK} šŸ’­ Cookware and bakeware is food preparation equipment, such as cooking pots, pans, baking sheets etc. used in kitchens. Cookware is used on a stove${ansi.RESET}`,
143
+ );
144
+ expect(output).toContain(
145
+ `${ansi.BRIGHT_BLACK} or range cooktop, while bakeware is used in an oven. Some utensils are considered both cookware and bakeware.${ansi.RESET}`,
146
+ );
147
+ expect(output).not.toContain("first");
148
+ });
127
149
  });
128
150
 
129
151
  describe("response parts", () => {
@@ -137,9 +159,7 @@ describe("render", () => {
137
159
  render(state);
138
160
 
139
161
  const output = write.mock.calls.map((c) => c[0]).join("");
140
- expect(output).toContain(
141
- `${ansi.WHITE_BACKGROUND}${ansi.BOLD_BLACK}*${ansi.RESET} Hello world`,
142
- );
162
+ expect(output).toContain(`šŸ’¬ Hello world`);
143
163
  });
144
164
  });
145
165
 
@@ -156,6 +176,27 @@ describe("render", () => {
156
176
  const output = write.mock.calls.map((c) => c[0]).join("");
157
177
  expect(output).toContain("bash: ls -la");
158
178
  });
179
+
180
+ it("multiple tool parts separated by inactive parts shouldn't have a blank line", () => {
181
+ const write = vi.fn();
182
+ const state = createMockState({
183
+ accumulatedResponse: [
184
+ { key: "xxx", title: "tool", text: "foo" },
185
+ { key: "xxx", title: "thinking", text: "need to do the stuff" },
186
+ { key: "xxx", title: "tool", text: "bar" },
187
+ { key: "xxx", title: "thinking", text: "ok, more stuff" },
188
+ { key: "xxx", title: "tool", text: "baz" },
189
+ ],
190
+ write,
191
+ });
192
+
193
+ render(state);
194
+
195
+ const output = stripANSI(write.mock.calls.map((c) => c[0]).join("")).replaceAll(" ", "");
196
+ console.log(output);
197
+ expect(output).toContain("foo\nbar\n\nšŸ’­ ok");
198
+ expect(output).toContain("stuff\n\nbaz");
199
+ });
159
200
  });
160
201
 
161
202
  describe("files parts", () => {
@@ -253,9 +294,7 @@ describe("render", () => {
253
294
  const output = write.mock.calls.map((c) => c[0]).join("");
254
295
  expect(output).not.toContain(`åˆ†ęžäø­`);
255
296
  expect(output).toContain(`bash: npm test`);
256
- expect(output).toContain(
257
- `${ansi.WHITE_BACKGROUND}${ansi.BOLD_BLACK}*${ansi.RESET} Test results: 5 passed`,
258
- );
297
+ expect(output).toContain(`šŸ’¬ Test results: 5 passed`);
259
298
  });
260
299
  });
261
300
  });