@patze/code-cli 0.24.0 → 0.41.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/CHANGELOG.md +162 -0
- package/VERSION +1 -1
- package/dist/backend/agent-trace.d.ts +1 -0
- package/dist/backend/agent-trace.d.ts.map +1 -1
- package/dist/backend/agent-trace.js +1 -0
- package/dist/backend/agent-trace.js.map +1 -1
- package/dist/backend/execute-client.d.ts +2 -0
- package/dist/backend/execute-client.d.ts.map +1 -1
- package/dist/backend/execute-client.js +2 -0
- package/dist/backend/execute-client.js.map +1 -1
- package/dist/backend/run-record.d.ts +2 -0
- package/dist/backend/run-record.d.ts.map +1 -1
- package/dist/backend/run-record.js +21 -0
- package/dist/backend/run-record.js.map +1 -1
- package/dist/backend/run-stream-client.d.ts +6 -0
- package/dist/backend/run-stream-client.d.ts.map +1 -1
- package/dist/backend/run-stream-client.js +10 -0
- package/dist/backend/run-stream-client.js.map +1 -1
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +22 -25
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/exec.d.ts.map +1 -1
- package/dist/cli/commands/exec.js +46 -8
- package/dist/cli/commands/exec.js.map +1 -1
- package/dist/cli/commands/trust-loop-snapshot.d.ts.map +1 -1
- package/dist/cli/commands/trust-loop-snapshot.js +19 -0
- package/dist/cli/commands/trust-loop-snapshot.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +9 -8
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interactive/agent-event-feed.d.ts +12 -2
- package/dist/cli/interactive/agent-event-feed.d.ts.map +1 -1
- package/dist/cli/interactive/agent-event-feed.js +182 -81
- package/dist/cli/interactive/agent-event-feed.js.map +1 -1
- package/dist/cli/interactive/agent-event-parse.d.ts +4 -0
- package/dist/cli/interactive/agent-event-parse.d.ts.map +1 -0
- package/dist/cli/interactive/agent-event-parse.js +92 -0
- package/dist/cli/interactive/agent-event-parse.js.map +1 -0
- package/dist/cli/interactive/agent-events.d.ts +6 -0
- package/dist/cli/interactive/agent-events.d.ts.map +1 -1
- package/dist/cli/interactive/agent-events.js +39 -2
- package/dist/cli/interactive/agent-events.js.map +1 -1
- package/dist/cli/interactive/agent-execute-turn.d.ts +3 -0
- package/dist/cli/interactive/agent-execute-turn.d.ts.map +1 -1
- package/dist/cli/interactive/agent-execute-turn.js +13 -0
- package/dist/cli/interactive/agent-execute-turn.js.map +1 -1
- package/dist/cli/interactive/agent-stream-format.d.ts +4 -0
- package/dist/cli/interactive/agent-stream-format.d.ts.map +1 -1
- package/dist/cli/interactive/agent-stream-format.js +44 -5
- package/dist/cli/interactive/agent-stream-format.js.map +1 -1
- package/dist/cli/interactive/agent-turn-classify.d.ts.map +1 -1
- package/dist/cli/interactive/agent-turn-classify.js +1 -3
- package/dist/cli/interactive/agent-turn-classify.js.map +1 -1
- package/dist/cli/interactive/agent-turn-context.d.ts.map +1 -1
- package/dist/cli/interactive/agent-turn-context.js +7 -1
- package/dist/cli/interactive/agent-turn-context.js.map +1 -1
- package/dist/cli/interactive/agent-turn-format.js +2 -2
- package/dist/cli/interactive/agent-turn-format.js.map +1 -1
- package/dist/cli/interactive/agent-turn.d.ts +1 -0
- package/dist/cli/interactive/agent-turn.d.ts.map +1 -1
- package/dist/cli/interactive/agent-turn.js +40 -28
- package/dist/cli/interactive/agent-turn.js.map +1 -1
- package/dist/cli/interactive/auth-gate.d.ts +6 -0
- package/dist/cli/interactive/auth-gate.d.ts.map +1 -0
- package/dist/cli/interactive/auth-gate.js +29 -0
- package/dist/cli/interactive/auth-gate.js.map +1 -0
- package/dist/cli/interactive/codex-diff-feed.d.ts +14 -0
- package/dist/cli/interactive/codex-diff-feed.d.ts.map +1 -0
- package/dist/cli/interactive/codex-diff-feed.js +76 -0
- package/dist/cli/interactive/codex-diff-feed.js.map +1 -0
- package/dist/cli/interactive/codex-feed-demo.d.ts +9 -0
- package/dist/cli/interactive/codex-feed-demo.d.ts.map +1 -0
- package/dist/cli/interactive/codex-feed-demo.js +72 -0
- package/dist/cli/interactive/codex-feed-demo.js.map +1 -0
- package/dist/cli/interactive/codex-feed-format.d.ts +35 -0
- package/dist/cli/interactive/codex-feed-format.d.ts.map +1 -0
- package/dist/cli/interactive/codex-feed-format.js +142 -0
- package/dist/cli/interactive/codex-feed-format.js.map +1 -0
- package/dist/cli/interactive/codex-feed-writer.d.ts +18 -0
- package/dist/cli/interactive/codex-feed-writer.d.ts.map +1 -0
- package/dist/cli/interactive/codex-feed-writer.js +144 -0
- package/dist/cli/interactive/codex-feed-writer.js.map +1 -0
- package/dist/cli/interactive/codex-preview-feed.d.ts +13 -0
- package/dist/cli/interactive/codex-preview-feed.d.ts.map +1 -0
- package/dist/cli/interactive/codex-preview-feed.js +130 -0
- package/dist/cli/interactive/codex-preview-feed.js.map +1 -0
- package/dist/cli/interactive/composer-chrome.d.ts +20 -0
- package/dist/cli/interactive/composer-chrome.d.ts.map +1 -0
- package/dist/cli/interactive/composer-chrome.js +77 -0
- package/dist/cli/interactive/composer-chrome.js.map +1 -0
- package/dist/cli/interactive/composer-keys.d.ts +2 -1
- package/dist/cli/interactive/composer-keys.d.ts.map +1 -1
- package/dist/cli/interactive/composer-keys.js +9 -2
- package/dist/cli/interactive/composer-keys.js.map +1 -1
- package/dist/cli/interactive/composer-session-chrome.d.ts +4 -0
- package/dist/cli/interactive/composer-session-chrome.d.ts.map +1 -0
- package/dist/cli/interactive/composer-session-chrome.js +22 -0
- package/dist/cli/interactive/composer-session-chrome.js.map +1 -0
- package/dist/cli/interactive/cookbook-feed-writer.d.ts +3 -0
- package/dist/cli/interactive/cookbook-feed-writer.d.ts.map +1 -0
- package/dist/cli/interactive/cookbook-feed-writer.js +3 -0
- package/dist/cli/interactive/cookbook-feed-writer.js.map +1 -0
- package/dist/cli/interactive/cookbook-tool-feed.d.ts +13 -0
- package/dist/cli/interactive/cookbook-tool-feed.d.ts.map +1 -0
- package/dist/cli/interactive/cookbook-tool-feed.js +67 -0
- package/dist/cli/interactive/cookbook-tool-feed.js.map +1 -0
- package/dist/cli/interactive/header.d.ts +4 -1
- package/dist/cli/interactive/header.d.ts.map +1 -1
- package/dist/cli/interactive/header.js +61 -1
- package/dist/cli/interactive/header.js.map +1 -1
- package/dist/cli/interactive/line-editor.d.ts +11 -0
- package/dist/cli/interactive/line-editor.d.ts.map +1 -1
- package/dist/cli/interactive/line-editor.js +83 -11
- package/dist/cli/interactive/line-editor.js.map +1 -1
- package/dist/cli/interactive/opentui-agent-event.d.ts +10 -0
- package/dist/cli/interactive/opentui-agent-event.d.ts.map +1 -0
- package/dist/cli/interactive/opentui-agent-event.js +12 -0
- package/dist/cli/interactive/opentui-agent-event.js.map +1 -0
- package/dist/cli/interactive/plain-agent-render.d.ts +5 -4
- package/dist/cli/interactive/plain-agent-render.d.ts.map +1 -1
- package/dist/cli/interactive/plain-agent-render.js +12 -44
- package/dist/cli/interactive/plain-agent-render.js.map +1 -1
- package/dist/cli/interactive/plain-codex-feed.d.ts +11 -0
- package/dist/cli/interactive/plain-codex-feed.d.ts.map +1 -0
- package/dist/cli/interactive/plain-codex-feed.js +37 -0
- package/dist/cli/interactive/plain-codex-feed.js.map +1 -0
- package/dist/cli/interactive/reset-agent.d.ts +8 -0
- package/dist/cli/interactive/reset-agent.d.ts.map +1 -0
- package/dist/cli/interactive/reset-agent.js +23 -0
- package/dist/cli/interactive/reset-agent.js.map +1 -0
- package/dist/cli/interactive/run-replay.d.ts.map +1 -1
- package/dist/cli/interactive/run-replay.js +3 -0
- package/dist/cli/interactive/run-replay.js.map +1 -1
- package/dist/cli/interactive/session-controller.d.ts +1 -0
- package/dist/cli/interactive/session-controller.d.ts.map +1 -1
- package/dist/cli/interactive/session-controller.js +24 -10
- package/dist/cli/interactive/session-controller.js.map +1 -1
- package/dist/cli/interactive/session-hints.d.ts +4 -0
- package/dist/cli/interactive/session-hints.d.ts.map +1 -0
- package/dist/cli/interactive/session-hints.js +28 -0
- package/dist/cli/interactive/session-hints.js.map +1 -0
- package/dist/cli/interactive/session.d.ts +3 -1
- package/dist/cli/interactive/session.d.ts.map +1 -1
- package/dist/cli/interactive/session.js +8 -2
- package/dist/cli/interactive/session.js.map +1 -1
- package/dist/cli/interactive/shell.d.ts.map +1 -1
- package/dist/cli/interactive/shell.js +61 -2
- package/dist/cli/interactive/shell.js.map +1 -1
- package/dist/cli/interactive/slash-dispatch.d.ts +6 -0
- package/dist/cli/interactive/slash-dispatch.d.ts.map +1 -1
- package/dist/cli/interactive/slash-dispatch.js +29 -5
- package/dist/cli/interactive/slash-dispatch.js.map +1 -1
- package/dist/cli/interactive/slash-menu-core.d.ts +10 -0
- package/dist/cli/interactive/slash-menu-core.d.ts.map +1 -0
- package/dist/cli/interactive/slash-menu-core.js +34 -0
- package/dist/cli/interactive/slash-menu-core.js.map +1 -0
- package/dist/cli/interactive/slash-menu.d.ts +3 -0
- package/dist/cli/interactive/slash-menu.d.ts.map +1 -1
- package/dist/cli/interactive/slash-menu.js +16 -4
- package/dist/cli/interactive/slash-menu.js.map +1 -1
- package/dist/cli/interactive/slash-registry.d.ts.map +1 -1
- package/dist/cli/interactive/slash-registry.js +4 -1
- package/dist/cli/interactive/slash-registry.js.map +1 -1
- package/dist/cli/interactive/trace-glyphs.d.ts +1 -0
- package/dist/cli/interactive/trace-glyphs.d.ts.map +1 -1
- package/dist/cli/interactive/trace-glyphs.js +47 -1
- package/dist/cli/interactive/trace-glyphs.js.map +1 -1
- package/dist/cli/interactive/transcript-upsert.d.ts.map +1 -1
- package/dist/cli/interactive/transcript-upsert.js +10 -6
- package/dist/cli/interactive/transcript-upsert.js.map +1 -1
- package/dist/cli/one-shot-args.d.ts +1 -0
- package/dist/cli/one-shot-args.d.ts.map +1 -1
- package/dist/cli/one-shot-args.js +3 -2
- package/dist/cli/one-shot-args.js.map +1 -1
- package/opentui/src/App.tsx +233 -85
- package/opentui/src/transcript-render.ts +144 -2
- package/package.json +1 -1
package/opentui/src/App.tsx
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "@opentui/react"
|
|
13
13
|
import { createElement, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
14
14
|
|
|
15
|
-
import { parseTranscriptLine } from "./line-parse.js"
|
|
16
15
|
import { importPatzeDist } from "./patze-dist.js"
|
|
17
16
|
import {
|
|
18
17
|
buildTranscriptLines,
|
|
@@ -23,7 +22,7 @@ import {
|
|
|
23
22
|
} from "./transcript-render.js"
|
|
24
23
|
import { classifyPlainOutput, createTranscriptSink, inferErrorKind } from "./tui-sink.js"
|
|
25
24
|
import { upsertTranscriptEntry } from "../../dist/cli/interactive/transcript-upsert.js"
|
|
26
|
-
import {
|
|
25
|
+
import { applyOpenTuiAgentEvent } from "../../dist/cli/interactive/opentui-agent-event.js"
|
|
27
26
|
import type { AgentEvent } from "../../dist/cli/interactive/agent-events.js"
|
|
28
27
|
|
|
29
28
|
extend({ "tui-input": InputRenderable })
|
|
@@ -58,6 +57,10 @@ type InteractiveController = {
|
|
|
58
57
|
session: {
|
|
59
58
|
getModelOverride: () => string | null
|
|
60
59
|
setModelOverride: (value: string | null) => void
|
|
60
|
+
getModelThinking: () => boolean | null
|
|
61
|
+
setModelThinking: (value: boolean | null) => void
|
|
62
|
+
getModelFast: () => boolean | null
|
|
63
|
+
setModelFast: (value: boolean | null) => void
|
|
61
64
|
getExecutionMode: () => "local" | "cloud"
|
|
62
65
|
snapshot: () => Array<{ input: string; lines: string[] }>
|
|
63
66
|
}
|
|
@@ -73,7 +76,7 @@ type InteractiveController = {
|
|
|
73
76
|
useActivitySpinner?: boolean
|
|
74
77
|
onAgentEvent?: (event: AgentEvent) => void
|
|
75
78
|
}
|
|
76
|
-
) => Promise<{ exitShell: boolean; exitCode: number; streamed?: boolean }>
|
|
79
|
+
) => Promise<{ exitShell: boolean; exitCode: number; streamed?: boolean; clearVisibleTranscript?: boolean }>
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
function parseCwdArg(): string {
|
|
@@ -107,10 +110,15 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
107
110
|
const [scrollOffset, setScrollOffset] = useState(0)
|
|
108
111
|
const [transcript, setTranscript] = useState<TranscriptEntry[]>([])
|
|
109
112
|
const [currentModel, setCurrentModel] = useState("composer-2.5-fast")
|
|
113
|
+
const [modelThinking, setModelThinking] = useState<boolean | null>(null)
|
|
114
|
+
const [modelFast, setModelFast] = useState<boolean | null>(null)
|
|
110
115
|
const [executionMode, setExecutionMode] = useState<"local" | "cloud">("local")
|
|
111
116
|
const [executionTarget, setExecutionTarget] = useState<string | null>(null)
|
|
112
117
|
const [supportedModels, setSupportedModels] = useState<string[]>([])
|
|
113
118
|
const [slashMenuReady, setSlashMenuReady] = useState(false)
|
|
119
|
+
const [startupHeader, setStartupHeader] = useState<string[]>([])
|
|
120
|
+
const [composerPlaceholder, setComposerPlaceholder] = useState("Describe a task… · / for commands")
|
|
121
|
+
const [emptySessionHints, setEmptySessionHints] = useState<string[]>([])
|
|
114
122
|
|
|
115
123
|
const followTranscript = useCallback(() => {
|
|
116
124
|
if (scrollPinnedRef.current) {
|
|
@@ -194,6 +202,14 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
194
202
|
streamingIdRef.current = null
|
|
195
203
|
}, [])
|
|
196
204
|
|
|
205
|
+
const openTuiStreamHandlers = useMemo(
|
|
206
|
+
() => ({
|
|
207
|
+
onAssistantDelta: upsertStreaming,
|
|
208
|
+
onClearAssistantStream: clearStreaming,
|
|
209
|
+
}),
|
|
210
|
+
[clearStreaming, upsertStreaming]
|
|
211
|
+
)
|
|
212
|
+
|
|
197
213
|
const refreshStatusMeta = useCallback(async () => {
|
|
198
214
|
const controller = controllerRef.current
|
|
199
215
|
if (!controller) {
|
|
@@ -213,7 +229,26 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
213
229
|
})
|
|
214
230
|
)
|
|
215
231
|
setExecutionMode(controller.session.getExecutionMode())
|
|
216
|
-
|
|
232
|
+
setModelThinking(controller.session.getModelThinking())
|
|
233
|
+
setModelFast(controller.session.getModelFast())
|
|
234
|
+
|
|
235
|
+
let modelIds = [...models.PATZE_SUPPORTED_MODELS]
|
|
236
|
+
const apiKey = process.env.CURSOR_API_KEY?.trim()
|
|
237
|
+
if (apiKey) {
|
|
238
|
+
try {
|
|
239
|
+
const { Cursor } = await import("@cursor/sdk")
|
|
240
|
+
const listed = await Cursor.models.list({ apiKey })
|
|
241
|
+
const ids = listed
|
|
242
|
+
.map((item) => String(item.id ?? "").trim())
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
if (ids.length > 0) {
|
|
245
|
+
modelIds = ids
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
// keep bundled fallback list
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
setSupportedModels(modelIds)
|
|
217
252
|
|
|
218
253
|
const cloudGit = await importPatzeDist<
|
|
219
254
|
typeof import("../../dist/cli/interactive/cloud-git.js")
|
|
@@ -226,6 +261,16 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
226
261
|
} else {
|
|
227
262
|
setExecutionTarget(null)
|
|
228
263
|
}
|
|
264
|
+
|
|
265
|
+
const authGate = await importPatzeDist<
|
|
266
|
+
typeof import("../../dist/cli/interactive/auth-gate.js")
|
|
267
|
+
>("cli/interactive/auth-gate.js")
|
|
268
|
+
setComposerPlaceholder(authGate.resolveComposerPlaceholder(controller.cwd))
|
|
269
|
+
|
|
270
|
+
const sessionHints = await importPatzeDist<
|
|
271
|
+
typeof import("../../dist/cli/interactive/session-hints.js")
|
|
272
|
+
>("cli/interactive/session-hints.js")
|
|
273
|
+
setEmptySessionHints(sessionHints.buildEmptySessionHints(controller.cwd))
|
|
229
274
|
}, [])
|
|
230
275
|
|
|
231
276
|
useEffect(() => {
|
|
@@ -254,10 +299,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
254
299
|
const controller = (await sessionMod.prepareInteractiveController({
|
|
255
300
|
cwd,
|
|
256
301
|
interactive: true,
|
|
257
|
-
streamPartials:
|
|
258
|
-
onPartial: (chunk: string) => {
|
|
259
|
-
upsertStreaming(chunk)
|
|
260
|
-
},
|
|
302
|
+
streamPartials: false,
|
|
261
303
|
})) as InteractiveController
|
|
262
304
|
|
|
263
305
|
if (cancelled) {
|
|
@@ -265,21 +307,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
265
307
|
}
|
|
266
308
|
|
|
267
309
|
controllerRef.current = controller
|
|
268
|
-
|
|
269
|
-
const headerEntries = controller.renderHeaderLines().flatMap((line) => {
|
|
270
|
-
const parsed = parseTranscriptLine(line)
|
|
271
|
-
if (!parsed || parsed.continuation) {
|
|
272
|
-
return []
|
|
273
|
-
}
|
|
274
|
-
return [
|
|
275
|
-
{
|
|
276
|
-
id: nextId(),
|
|
277
|
-
kind: "meta" as const,
|
|
278
|
-
label: parsed.label || "info",
|
|
279
|
-
text: parsed.text || line,
|
|
280
|
-
},
|
|
281
|
-
]
|
|
282
|
-
})
|
|
310
|
+
setStartupHeader(controller.renderHeaderLines())
|
|
283
311
|
|
|
284
312
|
const restored: TranscriptEntry[] = []
|
|
285
313
|
for (const turn of controller.session.snapshot()) {
|
|
@@ -294,7 +322,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
294
322
|
}
|
|
295
323
|
}
|
|
296
324
|
|
|
297
|
-
setTranscript(
|
|
325
|
+
setTranscript(restored)
|
|
298
326
|
await refreshStatusMeta()
|
|
299
327
|
setReady(true)
|
|
300
328
|
} catch (error) {
|
|
@@ -306,7 +334,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
306
334
|
return () => {
|
|
307
335
|
cancelled = true
|
|
308
336
|
}
|
|
309
|
-
}, [cwd, nextId, refreshStatusMeta
|
|
337
|
+
}, [cwd, nextId, refreshStatusMeta])
|
|
310
338
|
|
|
311
339
|
const exitApp = useCallback(() => {
|
|
312
340
|
controllerRef.current?.persistSession()
|
|
@@ -320,7 +348,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
320
348
|
return
|
|
321
349
|
}
|
|
322
350
|
|
|
323
|
-
const value = rawLine
|
|
351
|
+
const value = trimComposerPrompt(rawLine)
|
|
324
352
|
if (!value) {
|
|
325
353
|
return
|
|
326
354
|
}
|
|
@@ -351,16 +379,22 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
351
379
|
const result = await controller.processLine(value, sink, {
|
|
352
380
|
signal: abort.signal,
|
|
353
381
|
onActivity: setActivityLabel,
|
|
354
|
-
useActivitySpinner:
|
|
382
|
+
useActivitySpinner: true,
|
|
355
383
|
onAgentEvent: (event) => {
|
|
356
384
|
setActivityLabel(null)
|
|
357
|
-
setTranscript((items) =>
|
|
385
|
+
setTranscript((items) =>
|
|
386
|
+
applyOpenTuiAgentEvent(items, event, turnAssistantId, openTuiStreamHandlers)
|
|
387
|
+
)
|
|
358
388
|
followTranscript()
|
|
359
389
|
},
|
|
360
390
|
})
|
|
361
391
|
if (result.streamed) {
|
|
362
392
|
clearStreaming()
|
|
363
393
|
}
|
|
394
|
+
if (result.clearVisibleTranscript) {
|
|
395
|
+
setTranscript([])
|
|
396
|
+
markScrollPinned()
|
|
397
|
+
}
|
|
364
398
|
await refreshStatusMeta()
|
|
365
399
|
if (result.exitShell) {
|
|
366
400
|
exitApp()
|
|
@@ -380,6 +414,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
380
414
|
followTranscript,
|
|
381
415
|
markScrollPinned,
|
|
382
416
|
nextId,
|
|
417
|
+
openTuiStreamHandlers,
|
|
383
418
|
refreshStatusMeta,
|
|
384
419
|
upsertTranscriptLine,
|
|
385
420
|
upsertStreaming,
|
|
@@ -391,6 +426,23 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
391
426
|
setMode("model")
|
|
392
427
|
}, [])
|
|
393
428
|
|
|
429
|
+
const toggleModelPreference = useCallback((kind: "thinking" | "fast") => {
|
|
430
|
+
const controller = controllerRef.current
|
|
431
|
+
if (!controller) {
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
if (kind === "thinking") {
|
|
435
|
+
const next = controller.session.getModelThinking() === true ? null : true
|
|
436
|
+
controller.session.setModelThinking(next)
|
|
437
|
+
setModelThinking(next)
|
|
438
|
+
} else {
|
|
439
|
+
const next = controller.session.getModelFast() === true ? null : true
|
|
440
|
+
controller.session.setModelFast(next)
|
|
441
|
+
setModelFast(next)
|
|
442
|
+
}
|
|
443
|
+
controller.persistSession()
|
|
444
|
+
}, [])
|
|
445
|
+
|
|
394
446
|
const runCommand = useCallback(
|
|
395
447
|
async (rawCommand: string) => {
|
|
396
448
|
const trimmed = rawCommand.trim()
|
|
@@ -407,8 +459,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
407
459
|
|
|
408
460
|
switch (name?.toLowerCase()) {
|
|
409
461
|
case "help":
|
|
410
|
-
|
|
411
|
-
setMode("command")
|
|
462
|
+
await submitToController("/help")
|
|
412
463
|
return
|
|
413
464
|
case "model":
|
|
414
465
|
if (!args.trim()) {
|
|
@@ -431,7 +482,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
431
482
|
|
|
432
483
|
const submitInput = useCallback(
|
|
433
484
|
(value: string) => {
|
|
434
|
-
const prompt = value
|
|
485
|
+
const prompt = trimComposerPrompt(value)
|
|
435
486
|
setInput("")
|
|
436
487
|
|
|
437
488
|
if (!prompt || busy) {
|
|
@@ -473,41 +524,26 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
473
524
|
const modelSelectRows = Math.min(8, Math.max(3, rows - 10))
|
|
474
525
|
const commandPanelRows = 2 + commandSelectRows
|
|
475
526
|
const modelPanelRows = 3 + modelSelectRows
|
|
527
|
+
const headerRows = Math.max(1, startupHeader.length)
|
|
528
|
+
const composerChromeRows = 5
|
|
529
|
+
const bottomHintRows = 1
|
|
476
530
|
|
|
477
531
|
const transcriptViewportRows = Math.max(
|
|
478
532
|
4,
|
|
479
|
-
rows -
|
|
533
|
+
rows -
|
|
534
|
+
headerRows -
|
|
535
|
+
bottomHintRows -
|
|
536
|
+
(mode === "model"
|
|
537
|
+
? modelPanelRows + 2
|
|
538
|
+
: mode === "command"
|
|
539
|
+
? commandPanelRows + 2
|
|
540
|
+
: composerChromeRows)
|
|
480
541
|
)
|
|
481
542
|
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
id: "status-mode",
|
|
487
|
-
kind: "meta" as const,
|
|
488
|
-
label: "mode",
|
|
489
|
-
text: executionMode,
|
|
490
|
-
},
|
|
491
|
-
...(executionTarget
|
|
492
|
-
? [
|
|
493
|
-
{
|
|
494
|
-
id: "status-target",
|
|
495
|
-
kind: "meta" as const,
|
|
496
|
-
label: "target",
|
|
497
|
-
text: executionTarget,
|
|
498
|
-
},
|
|
499
|
-
]
|
|
500
|
-
: []),
|
|
501
|
-
{
|
|
502
|
-
id: "status-model",
|
|
503
|
-
kind: "meta" as const,
|
|
504
|
-
label: "model",
|
|
505
|
-
text: currentModel,
|
|
506
|
-
},
|
|
507
|
-
...transcript,
|
|
508
|
-
],
|
|
509
|
-
[cwd, currentModel, executionMode, executionTarget, transcript]
|
|
510
|
-
)
|
|
543
|
+
const composerModelLabel = `${currentModel}${modelThinking ? " · thinking" : ""}${modelFast ? " · fast" : ""}`
|
|
544
|
+
const composerPathLabel = shortenComposerPath(cwd)
|
|
545
|
+
|
|
546
|
+
const scrollableEntries = useMemo(() => transcript, [transcript])
|
|
511
547
|
|
|
512
548
|
const transcriptLines = useMemo(
|
|
513
549
|
() => buildTranscriptLines(scrollableEntries, columns),
|
|
@@ -548,6 +584,26 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
548
584
|
}))
|
|
549
585
|
}, [currentModel, modelSearch, supportedModels])
|
|
550
586
|
|
|
587
|
+
const toggleFeedExpand = useCallback((target: "think" | "diff") => {
|
|
588
|
+
setTranscript((items) => {
|
|
589
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
590
|
+
const entry = items[index]
|
|
591
|
+
const matchesThink = target === "think" && entry.label === "think" && entry.detailText
|
|
592
|
+
const matchesDiff =
|
|
593
|
+
target === "diff" && entry.kind === "meta" && entry.label === "diff" && entry.detailText
|
|
594
|
+
if (matchesThink || matchesDiff) {
|
|
595
|
+
return items.map((candidate, candidateIndex) =>
|
|
596
|
+
candidateIndex === index
|
|
597
|
+
? { ...candidate, expanded: !candidate.expanded }
|
|
598
|
+
: candidate
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return items
|
|
603
|
+
})
|
|
604
|
+
followTranscript()
|
|
605
|
+
}, [followTranscript])
|
|
606
|
+
|
|
551
607
|
useKeyboard((key: KeyEvent) => {
|
|
552
608
|
const character = getInputCharacter(key)
|
|
553
609
|
|
|
@@ -571,6 +627,10 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
571
627
|
if (mode === "model") {
|
|
572
628
|
if (key.name === "backspace" || key.name === "delete") {
|
|
573
629
|
setModelSearch((value) => value.slice(0, -1))
|
|
630
|
+
} else if (character === "T") {
|
|
631
|
+
toggleModelPreference("thinking")
|
|
632
|
+
} else if (character === "F") {
|
|
633
|
+
toggleModelPreference("fast")
|
|
574
634
|
} else if (isSearchInput(character)) {
|
|
575
635
|
setModelSearch((value) => `${value}${character}`)
|
|
576
636
|
}
|
|
@@ -586,6 +646,21 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
586
646
|
if (mode === "input") {
|
|
587
647
|
const pageSize = Math.max(1, transcriptViewportRows - 1)
|
|
588
648
|
|
|
649
|
+
if (key.ctrl && key.name === "t") {
|
|
650
|
+
toggleFeedExpand("think")
|
|
651
|
+
return
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (key.ctrl && key.name === "d") {
|
|
655
|
+
toggleFeedExpand("diff")
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (key.shift && (key.name === "return" || key.name === "enter")) {
|
|
660
|
+
setInput((value) => `${value}\n`)
|
|
661
|
+
return
|
|
662
|
+
}
|
|
663
|
+
|
|
589
664
|
if (key.name === "up") {
|
|
590
665
|
markScrollUnpinned()
|
|
591
666
|
setScrollOffset((offset) => Math.min(maxScrollOffset, offset + 1))
|
|
@@ -646,9 +721,27 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
646
721
|
return createElement(
|
|
647
722
|
"box",
|
|
648
723
|
{ flexDirection: "column", height: rows, paddingX: 1 },
|
|
724
|
+
startupHeader.length
|
|
725
|
+
? createElement(
|
|
726
|
+
"box",
|
|
727
|
+
{ flexDirection: "column", flexShrink: 0, marginBottom: 1 },
|
|
728
|
+
startupHeader.map((line, index) =>
|
|
729
|
+
createElement(HeaderLine, { key: `header-${index}`, line })
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
: null,
|
|
649
733
|
createElement(
|
|
650
734
|
"box",
|
|
651
735
|
{ flexDirection: "column", height: transcriptViewportRows },
|
|
736
|
+
transcript.length === 0 && emptySessionHints.length
|
|
737
|
+
? emptySessionHints.map((hint, index) =>
|
|
738
|
+
createElement("text", {
|
|
739
|
+
key: `hint-${index}`,
|
|
740
|
+
content: hint,
|
|
741
|
+
attributes: TextAttributes.DIM,
|
|
742
|
+
})
|
|
743
|
+
)
|
|
744
|
+
: null,
|
|
652
745
|
visibleTranscriptLines.map((line) =>
|
|
653
746
|
createElement(TranscriptLine, { key: line.id, line })
|
|
654
747
|
)
|
|
@@ -662,6 +755,13 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
662
755
|
}`
|
|
663
756
|
)
|
|
664
757
|
: null,
|
|
758
|
+
busy && activityLabel
|
|
759
|
+
? createElement("text", {
|
|
760
|
+
content: `${["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"][activityFrame % 10]} ${activityLabel}…`,
|
|
761
|
+
fg: "yellow",
|
|
762
|
+
attributes: TextAttributes.DIM,
|
|
763
|
+
})
|
|
764
|
+
: null,
|
|
665
765
|
mode === "command"
|
|
666
766
|
? createElement(
|
|
667
767
|
"box",
|
|
@@ -705,7 +805,7 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
705
805
|
attributes: TextAttributes.BOLD,
|
|
706
806
|
}),
|
|
707
807
|
createElement("text", {
|
|
708
|
-
content:
|
|
808
|
+
content: `Type to search · T thinking ${modelThinking ? "on" : "off"} · F fast ${modelFast ? "on" : "off"} · Enter choose · Escape cancel`,
|
|
709
809
|
fg: "gray",
|
|
710
810
|
}),
|
|
711
811
|
createElement("text", {
|
|
@@ -728,42 +828,90 @@ export function App({ cwd: cwdArg }: { cwd?: string }) {
|
|
|
728
828
|
border: true,
|
|
729
829
|
borderStyle: "single",
|
|
730
830
|
borderColor: busy ? "yellow" : "green",
|
|
831
|
+
flexDirection: "column",
|
|
731
832
|
marginTop: 1,
|
|
732
833
|
paddingX: 1,
|
|
733
834
|
},
|
|
734
|
-
createElement(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
835
|
+
createElement(
|
|
836
|
+
"box",
|
|
837
|
+
{ flexDirection: "row", alignItems: "center" },
|
|
838
|
+
createElement("text", {
|
|
839
|
+
content: "> ",
|
|
840
|
+
attributes: TextAttributes.BOLD,
|
|
841
|
+
}),
|
|
842
|
+
createElement(TuiInput, {
|
|
843
|
+
focused: ready && !busy,
|
|
844
|
+
placeholder: busy
|
|
845
|
+
? activityLabel
|
|
846
|
+
? `${["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"][activityFrame % 10]} ${activityLabel}… · Ctrl+C cancel`
|
|
847
|
+
: "Ctrl+C cancel · waiting for agent…"
|
|
848
|
+
: ready
|
|
849
|
+
? composerPlaceholder
|
|
850
|
+
: "Loading Patze Code…",
|
|
851
|
+
value: input,
|
|
852
|
+
onInput: (value: string) => {
|
|
853
|
+
setInput(value)
|
|
854
|
+
if (busy) {
|
|
855
|
+
return
|
|
856
|
+
}
|
|
857
|
+
if (value.startsWith("/") && !value.includes(" ")) {
|
|
858
|
+
setMode("command")
|
|
859
|
+
} else {
|
|
860
|
+
setMode("input")
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
onSubmit: submitInput,
|
|
864
|
+
})
|
|
865
|
+
),
|
|
866
|
+
createElement("text", {
|
|
867
|
+
content: `${composerModelLabel} · ${composerPathLabel}`,
|
|
868
|
+
attributes: TextAttributes.DIM,
|
|
756
869
|
})
|
|
757
870
|
),
|
|
758
871
|
createElement("text", {
|
|
759
872
|
content: busy
|
|
760
873
|
? "Esc exit · Ctrl+C cancel run · agent executing"
|
|
761
|
-
: "Esc exit · Ctrl+C quit · /
|
|
874
|
+
: "Esc exit · Ctrl+C quit · / commands · ctrl+t thought · ctrl+d diff · /status",
|
|
762
875
|
attributes: TextAttributes.DIM,
|
|
763
876
|
})
|
|
764
877
|
)
|
|
765
878
|
}
|
|
766
879
|
|
|
880
|
+
function trimComposerPrompt(value: string): string {
|
|
881
|
+
return value.replace(/^\s+|\s+$/g, "")
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function shortenComposerPath(path: string): string {
|
|
885
|
+
const normalized = String(path || "").trim().replace(/\\/g, "/")
|
|
886
|
+
if ([...normalized].length <= 36) {
|
|
887
|
+
return normalized
|
|
888
|
+
}
|
|
889
|
+
const parts = normalized.split("/").filter(Boolean)
|
|
890
|
+
if (parts.length >= 2) {
|
|
891
|
+
const tail = `${parts[parts.length - 2]}/${parts[parts.length - 1]}`
|
|
892
|
+
const candidate = `…/${tail}`
|
|
893
|
+
if ([...candidate].length <= 36) {
|
|
894
|
+
return candidate
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return `…${normalized.slice(-35)}`
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function HeaderLine({ line }: { line: string }) {
|
|
901
|
+
const plain = stripAnsi(line)
|
|
902
|
+
const dim = line.includes("\x1b[2m") || plain.startsWith("Tip:") || plain.includes("Beta ·")
|
|
903
|
+
const warn = line.includes("\x1b[33m") || plain.startsWith("Heads up:")
|
|
904
|
+
return createElement("text", {
|
|
905
|
+
content: plain,
|
|
906
|
+
fg: warn ? "yellow" : dim ? "gray" : undefined,
|
|
907
|
+
attributes: dim ? TextAttributes.DIM : TextAttributes.NONE,
|
|
908
|
+
})
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function stripAnsi(text: string): string {
|
|
912
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "")
|
|
913
|
+
}
|
|
914
|
+
|
|
767
915
|
function TranscriptLine({ line }: { line: TranscriptLine }) {
|
|
768
916
|
const color = {
|
|
769
917
|
assistant: "white",
|
|
@@ -12,6 +12,9 @@ export type TranscriptEntry = {
|
|
|
12
12
|
label: string
|
|
13
13
|
text: string
|
|
14
14
|
upsertKey?: string
|
|
15
|
+
/** Full thinking body when collapsed summary is truncated. */
|
|
16
|
+
detailText?: string
|
|
17
|
+
expanded?: boolean
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export type TranscriptPart = {
|
|
@@ -37,17 +40,65 @@ export function buildTranscriptLines(entries: TranscriptEntry[], columns: number
|
|
|
37
40
|
entry.kind === "assistant"
|
|
38
41
|
? renderMarkdownLines(entry.text || "...", textWidth)
|
|
39
42
|
: wrapText(entry.text || "...", textWidth).map((text) => ({
|
|
40
|
-
parts:
|
|
43
|
+
parts: parseCodexFeedParts(entry.kind, entry.label, text),
|
|
41
44
|
}))
|
|
42
45
|
)
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
const lines = renderedLines.map((line, index) => ({
|
|
45
48
|
id: `${entry.id}-${index}`,
|
|
46
49
|
kind: entry.kind,
|
|
47
50
|
label: index === 0 ? entry.label : "",
|
|
48
51
|
parts: line.parts,
|
|
49
52
|
}))
|
|
53
|
+
|
|
54
|
+
if (entry.label === "think" && entry.detailText) {
|
|
55
|
+
return appendExpandableDetailLines(lines, entry, textWidth, {
|
|
56
|
+
hint: ` (+${entry.detailText.length} chars · ctrl+t expand)`,
|
|
57
|
+
renderDetailLine: (text) => [{ text: ` ${text}`, color: "gray" as const, dimColor: true }],
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (entry.kind === "meta" && entry.label === "diff" && entry.detailText) {
|
|
62
|
+
return appendExpandableDetailLines(lines, entry, textWidth, {
|
|
63
|
+
hint: ` (+${entry.detailText.split("\n").length} lines · ctrl+d expand)`,
|
|
64
|
+
renderDetailLine: (text) => parseCodexDiffParts(text.startsWith(" ") ? text : ` ${text}`),
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return lines
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function appendExpandableDetailLines(
|
|
73
|
+
lines: TranscriptLine[],
|
|
74
|
+
entry: TranscriptEntry,
|
|
75
|
+
textWidth: number,
|
|
76
|
+
options: {
|
|
77
|
+
hint: string
|
|
78
|
+
renderDetailLine: (text: string) => TranscriptPart[]
|
|
79
|
+
}
|
|
80
|
+
): TranscriptLine[] {
|
|
81
|
+
if (entry.expanded) {
|
|
82
|
+
const detailLines = entry.detailText!
|
|
83
|
+
.split("\n")
|
|
84
|
+
.flatMap((rawLine, index) =>
|
|
85
|
+
wrapText(rawLine, textWidth - 2).map((text, wrapIndex) => ({
|
|
86
|
+
id: `${entry.id}-detail-${index}-${wrapIndex}`,
|
|
87
|
+
kind: entry.kind,
|
|
88
|
+
label: "",
|
|
89
|
+
parts: options.renderDetailLine(text),
|
|
90
|
+
}))
|
|
91
|
+
)
|
|
92
|
+
return [...lines, ...detailLines]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
lines.push({
|
|
96
|
+
id: `${entry.id}-expand-hint`,
|
|
97
|
+
kind: entry.kind,
|
|
98
|
+
label: "",
|
|
99
|
+
parts: [{ text: options.hint, color: "gray", dimColor: true }],
|
|
50
100
|
})
|
|
101
|
+
return lines
|
|
51
102
|
}
|
|
52
103
|
|
|
53
104
|
function trimTrailingBlankLines(lines: Array<{ parts: TranscriptPart[] }>) {
|
|
@@ -156,6 +207,97 @@ function renderMarkdownLines(value: string, width: number) {
|
|
|
156
207
|
return lines
|
|
157
208
|
}
|
|
158
209
|
|
|
210
|
+
function parseCodexFeedParts(kind: TranscriptEntryKind, label: string, text: string): TranscriptPart[] {
|
|
211
|
+
if (kind === "meta" && label === "diff") {
|
|
212
|
+
return parseCodexDiffParts(text)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (kind === "tool") {
|
|
216
|
+
const match = text.match(/^([…✓✗])\s+(.*)$/)
|
|
217
|
+
if (match) {
|
|
218
|
+
const glyph = match[1]
|
|
219
|
+
const body = match[2] ?? ""
|
|
220
|
+
const glyphColor =
|
|
221
|
+
glyph === "✓" ? "green" : glyph === "✗" ? "red" : ("yellow" as const)
|
|
222
|
+
const bodyParts = parseCodexToolBodyParts(body)
|
|
223
|
+
return [{ text: `${glyph} `, color: glyphColor, bold: true }, ...bodyParts]
|
|
224
|
+
}
|
|
225
|
+
return parseCodexToolBodyParts(text)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (kind === "status" || (kind === "meta" && label === "done")) {
|
|
229
|
+
if (text.startsWith("→ ")) {
|
|
230
|
+
return [{ text, color: "cyan" }]
|
|
231
|
+
}
|
|
232
|
+
if (text.startsWith("…")) {
|
|
233
|
+
return [{ text, color: "gray", dimColor: true }]
|
|
234
|
+
}
|
|
235
|
+
if (text.startsWith("✓")) {
|
|
236
|
+
return [{ text, color: "green" }]
|
|
237
|
+
}
|
|
238
|
+
if (text.startsWith("✗")) {
|
|
239
|
+
return [{ text, color: "red" }]
|
|
240
|
+
}
|
|
241
|
+
if (text.startsWith("○")) {
|
|
242
|
+
return [{ text, color: "yellow" }]
|
|
243
|
+
}
|
|
244
|
+
if (text.startsWith("●")) {
|
|
245
|
+
return [{ text, color: "cyan", bold: true }]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (kind === "status" && label === "think") {
|
|
250
|
+
if (text.startsWith("…")) {
|
|
251
|
+
return [{ text, color: "gray", dimColor: true }]
|
|
252
|
+
}
|
|
253
|
+
if (text.startsWith("✓")) {
|
|
254
|
+
return [{ text, color: "green" }]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (kind === "meta") {
|
|
259
|
+
if (text.startsWith("✓ completed")) {
|
|
260
|
+
return [{ text, color: "green", bold: true }]
|
|
261
|
+
}
|
|
262
|
+
if (text.startsWith("deliverables")) {
|
|
263
|
+
return [{ text, color: "cyan" }]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return [{ text }]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function parseCodexToolBodyParts(body: string): TranscriptPart[] {
|
|
271
|
+
if (body.startsWith("+ create")) {
|
|
272
|
+
return [{ text: body, color: "green" }]
|
|
273
|
+
}
|
|
274
|
+
if (body.startsWith("~ modify")) {
|
|
275
|
+
return [{ text: body, color: "yellow" }]
|
|
276
|
+
}
|
|
277
|
+
if (body.startsWith("- delete")) {
|
|
278
|
+
return [{ text: body, color: "red" }]
|
|
279
|
+
}
|
|
280
|
+
if (body.startsWith("▸")) {
|
|
281
|
+
return [{ text: body, color: "magenta" }]
|
|
282
|
+
}
|
|
283
|
+
return [{ text: body }]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function parseCodexDiffParts(text: string): TranscriptPart[] {
|
|
287
|
+
const trimmed = text.trim()
|
|
288
|
+
if (trimmed === "…") {
|
|
289
|
+
return [{ text: " …", color: "gray", dimColor: true }]
|
|
290
|
+
}
|
|
291
|
+
const line = text.startsWith(" ") ? text : ` ${text}`
|
|
292
|
+
if (line.startsWith(" - ")) {
|
|
293
|
+
return [{ text: line, color: "red" }]
|
|
294
|
+
}
|
|
295
|
+
if (line.startsWith(" + ")) {
|
|
296
|
+
return [{ text: line, color: "green" }]
|
|
297
|
+
}
|
|
298
|
+
return [{ text: line, color: "gray", dimColor: true }]
|
|
299
|
+
}
|
|
300
|
+
|
|
159
301
|
function wrapText(value: string, width: number) {
|
|
160
302
|
const lines: string[] = []
|
|
161
303
|
|