jfl 0.6.1 → 0.6.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.
package/package.json
CHANGED
|
@@ -12,13 +12,14 @@ import type { PiTheme } from "./types.js"
|
|
|
12
12
|
|
|
13
13
|
// ─── Shared helpers ──────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
|
+
const MAX_LINE_W = 140
|
|
16
|
+
|
|
15
17
|
function visibleLen(text: string): number {
|
|
16
18
|
return text.replace(/\x1b\[[0-9;]*m/g, "").length
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
function truncLine(text: string, maxW: number): string {
|
|
21
|
+
function truncLine(text: string, maxW: number = MAX_LINE_W): string {
|
|
20
22
|
if (visibleLen(text) <= maxW) return text
|
|
21
|
-
// Walk char-by-char, skipping ANSI sequences, until we hit maxW visible chars
|
|
22
23
|
let visible = 0
|
|
23
24
|
let i = 0
|
|
24
25
|
while (i < text.length && visible < maxW - 1) {
|
|
@@ -32,6 +33,10 @@ function truncLine(text: string, maxW: number): string {
|
|
|
32
33
|
return text.slice(0, i) + "\x1b[0m…"
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function safeRender(lines: string[]): string[] {
|
|
37
|
+
return lines.map(l => truncLine(l, MAX_LINE_W))
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
function wrapText(text: string, width: number): string[] {
|
|
36
41
|
const lines: string[] = []
|
|
37
42
|
for (const raw of text.split("\n")) {
|
|
@@ -55,7 +60,7 @@ function sectionBorder(theme: PiTheme, label: string, width: number): string {
|
|
|
55
60
|
|
|
56
61
|
export function hudRenderCall(args: Record<string, any>, theme: PiTheme): any {
|
|
57
62
|
const label = theme.fg("accent", "◆") + " " + theme.fg("toolTitle", theme.bold("HUD"))
|
|
58
|
-
return { render: () => [label], invalidate() {} }
|
|
63
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export function hudRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -65,7 +70,7 @@ export function hudRenderResult(result: any, opts: { expanded: boolean }, theme:
|
|
|
65
70
|
if (!opts.expanded && lines.length > 5) {
|
|
66
71
|
const display = lines.slice(0, 5).map(l => theme.fg("toolOutput", l))
|
|
67
72
|
display.push(theme.fg("dim", `... ${lines.length - 5} more lines (Ctrl+O)`))
|
|
68
|
-
return { render: () => display, invalidate() {} }
|
|
73
|
+
return { render: () => safeRender(display), invalidate() {} }
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
const themed = lines.map(line => {
|
|
@@ -75,7 +80,7 @@ export function hudRenderResult(result: any, opts: { expanded: boolean }, theme:
|
|
|
75
80
|
return theme.fg("toolOutput", line)
|
|
76
81
|
})
|
|
77
82
|
|
|
78
|
-
return { render: () => themed, invalidate() {} }
|
|
83
|
+
return { render: () => safeRender(themed), invalidate() {} }
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
// ─── Context Tool ───────────────────────────────────────────────────────────
|
|
@@ -83,14 +88,14 @@ export function hudRenderResult(result: any, opts: { expanded: boolean }, theme:
|
|
|
83
88
|
export function contextRenderCall(args: Record<string, any>, theme: PiTheme): any {
|
|
84
89
|
const query = args.query ?? ""
|
|
85
90
|
const label = theme.fg("toolTitle", theme.bold("context ")) + theme.fg("accent", `"${query}"`)
|
|
86
|
-
return { render: () => [label], invalidate() {} }
|
|
91
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
export function contextRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
90
95
|
const MAX_W = 140
|
|
91
96
|
const raw = extractText(result)
|
|
92
97
|
if (raw === "No relevant context found." || raw === "No context available.") {
|
|
93
|
-
return { render: () => [theme.fg("dim", "No relevant context found")], invalidate() {} }
|
|
98
|
+
return { render: () => safeRender([theme.fg("dim", "No relevant context found")]), invalidate() {} }
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
const sections = raw.split(/---\n/).filter(Boolean)
|
|
@@ -124,7 +129,7 @@ export function contextRenderResult(result: any, opts: { expanded: boolean }, th
|
|
|
124
129
|
lines.push(theme.fg("dim", `... ${sections.length - 3} more results (Ctrl+O)`))
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
return { render: () => lines, invalidate() {} }
|
|
132
|
+
return { render: () => safeRender(lines), invalidate() {} }
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
// ─── CRM Tool ───────────────────────────────────────────────────────────────
|
|
@@ -133,13 +138,13 @@ export function crmRenderCall(args: Record<string, any>, theme: PiTheme): any {
|
|
|
133
138
|
const cmd = args.command ?? "list"
|
|
134
139
|
const extra = args.args ? ` ${args.args}` : ""
|
|
135
140
|
const label = theme.fg("toolTitle", theme.bold("crm ")) + theme.fg("accent", cmd) + theme.fg("dim", extra)
|
|
136
|
-
return { render: () => [label], invalidate() {} }
|
|
141
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
export function crmRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
140
145
|
const raw = extractText(result)
|
|
141
146
|
if (raw.startsWith("Error")) {
|
|
142
|
-
return { render: () => [theme.fg("error", raw)], invalidate() {} }
|
|
147
|
+
return { render: () => safeRender([theme.fg("error", raw)]), invalidate() {} }
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
const lines = raw.split("\n").filter(Boolean)
|
|
@@ -154,10 +159,10 @@ export function crmRenderResult(result: any, opts: { expanded: boolean }, theme:
|
|
|
154
159
|
if (!opts.expanded && themed.length > 8) {
|
|
155
160
|
const display = themed.slice(0, 8)
|
|
156
161
|
display.push(theme.fg("dim", `... ${themed.length - 8} more (Ctrl+O)`))
|
|
157
|
-
return { render: () => display, invalidate() {} }
|
|
162
|
+
return { render: () => safeRender(display), invalidate() {} }
|
|
158
163
|
}
|
|
159
164
|
|
|
160
|
-
return { render: () => themed, invalidate() {} }
|
|
165
|
+
return { render: () => safeRender(themed), invalidate() {} }
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
// ─── Memory Search Tool ─────────────────────────────────────────────────────
|
|
@@ -166,13 +171,13 @@ export function memoryRenderCall(args: Record<string, any>, theme: PiTheme): any
|
|
|
166
171
|
const query = args.query ?? ""
|
|
167
172
|
const type = args.type && args.type !== "all" ? ` [${args.type}]` : ""
|
|
168
173
|
const label = theme.fg("toolTitle", theme.bold("memory ")) + theme.fg("accent", `"${query}"`) + theme.fg("muted", type)
|
|
169
|
-
return { render: () => [label], invalidate() {} }
|
|
174
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
export function memoryRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
173
178
|
const raw = extractText(result)
|
|
174
179
|
if (raw.includes("unavailable") || raw.includes("No memories")) {
|
|
175
|
-
return { render: () => [theme.fg("dim", raw)], invalidate() {} }
|
|
180
|
+
return { render: () => safeRender([theme.fg("dim", raw)]), invalidate() {} }
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
const entries = raw.split(/\n\n---\n\n/).filter(Boolean)
|
|
@@ -195,7 +200,7 @@ export function memoryRenderResult(result: any, opts: { expanded: boolean }, the
|
|
|
195
200
|
lines.push(theme.fg("dim", `... ${entries.length - 4} more (Ctrl+O)`))
|
|
196
201
|
}
|
|
197
202
|
|
|
198
|
-
return { render: () => lines, invalidate() {} }
|
|
203
|
+
return { render: () => safeRender(lines), invalidate() {} }
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
// ─── Synopsis Tool ──────────────────────────────────────────────────────────
|
|
@@ -204,7 +209,7 @@ export function synopsisRenderCall(args: Record<string, any>, theme: PiTheme): a
|
|
|
204
209
|
const hours = args.hours ?? 24
|
|
205
210
|
const author = args.author ? ` by ${args.author}` : ""
|
|
206
211
|
const label = theme.fg("toolTitle", theme.bold("synopsis ")) + theme.fg("accent", `${hours}h`) + theme.fg("muted", author)
|
|
207
|
-
return { render: () => [label], invalidate() {} }
|
|
212
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
export function synopsisRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -225,17 +230,17 @@ export function synopsisRenderResult(result: any, opts: { expanded: boolean }, t
|
|
|
225
230
|
if (!opts.expanded && themed.length > 15) {
|
|
226
231
|
const display = themed.slice(0, 15)
|
|
227
232
|
display.push(theme.fg("dim", `... ${themed.length - 15} more lines (Ctrl+O)`))
|
|
228
|
-
return { render: () => display, invalidate() {} }
|
|
233
|
+
return { render: () => safeRender(display), invalidate() {} }
|
|
229
234
|
}
|
|
230
235
|
|
|
231
|
-
return { render: () => themed, invalidate() {} }
|
|
236
|
+
return { render: () => safeRender(themed), invalidate() {} }
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
// ─── Eval Status Tool ───────────────────────────────────────────────────────
|
|
235
240
|
|
|
236
241
|
export function evalStatusRenderCall(args: Record<string, any>, theme: PiTheme): any {
|
|
237
242
|
const label = theme.fg("toolTitle", theme.bold("eval")) + " " + theme.fg("accent", "status")
|
|
238
|
-
return { render: () => [label], invalidate() {} }
|
|
243
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
export function evalStatusRenderResult(result: any, opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -253,7 +258,7 @@ export function evalStatusRenderResult(result: any, opts: { expanded: boolean },
|
|
|
253
258
|
return theme.fg("toolOutput", line)
|
|
254
259
|
})
|
|
255
260
|
|
|
256
|
-
return { render: () => themed, invalidate() {} }
|
|
261
|
+
return { render: () => safeRender(themed), invalidate() {} }
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
// ─── Eval Compare Tool ──────────────────────────────────────────────────────
|
|
@@ -262,7 +267,7 @@ export function evalCompareRenderCall(args: Record<string, any>, theme: PiTheme)
|
|
|
262
267
|
const a = args.a ?? "-2"
|
|
263
268
|
const b = args.b ?? "-1"
|
|
264
269
|
const label = theme.fg("toolTitle", theme.bold("eval")) + " " + theme.fg("accent", `compare ${a} ↔ ${b}`)
|
|
265
|
-
return { render: () => [label], invalidate() {} }
|
|
270
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
export function evalCompareRenderResult(result: any, _opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -273,7 +278,7 @@ export function evalCompareRenderResult(result: any, _opts: { expanded: boolean
|
|
|
273
278
|
if (/unchanged|same/i.test(line)) return theme.fg("dim", line)
|
|
274
279
|
return theme.fg("toolOutput", line)
|
|
275
280
|
})
|
|
276
|
-
return { render: () => lines, invalidate() {} }
|
|
281
|
+
return { render: () => safeRender(lines), invalidate() {} }
|
|
277
282
|
}
|
|
278
283
|
|
|
279
284
|
// ─── Policy Score Tool ──────────────────────────────────────────────────────
|
|
@@ -282,7 +287,7 @@ export function policyScoreRenderCall(args: Record<string, any>, theme: PiTheme)
|
|
|
282
287
|
const action = args.action_type ?? "?"
|
|
283
288
|
const scope = args.scope ? `[${args.scope}]` : ""
|
|
284
289
|
const label = theme.fg("toolTitle", theme.bold("policy ")) + theme.fg("accent", action) + " " + theme.fg("dim", scope)
|
|
285
|
-
return { render: () => [label], invalidate() {} }
|
|
290
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
export function policyScoreRenderResult(result: any, _opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -293,7 +298,7 @@ export function policyScoreRenderResult(result: any, _opts: { expanded: boolean
|
|
|
293
298
|
if (/delta|score|predict/i.test(line)) return theme.fg("accent", line)
|
|
294
299
|
return theme.fg("toolOutput", line)
|
|
295
300
|
})
|
|
296
|
-
return { render: () => lines, invalidate() {} }
|
|
301
|
+
return { render: () => safeRender(lines), invalidate() {} }
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
// ─── Policy Rank Tool ───────────────────────────────────────────────────────
|
|
@@ -302,7 +307,7 @@ export function policyRankRenderCall(args: Record<string, any>, theme: PiTheme):
|
|
|
302
307
|
let count = 0
|
|
303
308
|
try { count = JSON.parse(args.actions ?? "[]").length } catch {}
|
|
304
309
|
const label = theme.fg("toolTitle", theme.bold("policy ")) + theme.fg("accent", `rank ${count} actions`)
|
|
305
|
-
return { render: () => [label], invalidate() {} }
|
|
310
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
export function policyRankRenderResult(result: any, _opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -313,7 +318,7 @@ export function policyRankRenderResult(result: any, _opts: { expanded: boolean }
|
|
|
313
318
|
if (/delta|score/i.test(line)) return `${medal} ${theme.fg("accent", line)}`
|
|
314
319
|
return `${medal} ${theme.fg("toolOutput", line)}`
|
|
315
320
|
})
|
|
316
|
-
return { render: () => themed, invalidate() {} }
|
|
321
|
+
return { render: () => safeRender(themed), invalidate() {} }
|
|
317
322
|
}
|
|
318
323
|
|
|
319
324
|
// ─── Training Buffer Tool ───────────────────────────────────────────────────
|
|
@@ -323,7 +328,7 @@ export function trainingBufferRenderCall(args: Record<string, any>, theme: PiThe
|
|
|
323
328
|
const outcome = args.outcome ?? ""
|
|
324
329
|
const icon = outcome === "improved" ? theme.fg("success", "↑") : outcome === "regressed" ? theme.fg("error", "↓") : theme.fg("dim", "→")
|
|
325
330
|
const label = theme.fg("toolTitle", theme.bold("train ")) + `${icon} ${theme.fg("accent", action)}`
|
|
326
|
-
return { render: () => [label], invalidate() {} }
|
|
331
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
export function trainingBufferRenderResult(result: any, _opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -331,7 +336,7 @@ export function trainingBufferRenderResult(result: any, _opts: { expanded: boole
|
|
|
331
336
|
const line = raw.includes("recorded") || raw.includes("saved")
|
|
332
337
|
? theme.fg("success", "✓ ") + theme.fg("muted", raw)
|
|
333
338
|
: theme.fg("toolOutput", raw)
|
|
334
|
-
return { render: () => [line], invalidate() {} }
|
|
339
|
+
return { render: () => safeRender([line]), invalidate() {} }
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
// ─── Mine Tuples Tool ───────────────────────────────────────────────────────
|
|
@@ -340,7 +345,7 @@ export function mineTuplesRenderCall(args: Record<string, any>, theme: PiTheme):
|
|
|
340
345
|
const source = args.source ?? "all"
|
|
341
346
|
const write = args.write === "yes" ? theme.fg("warning", " (write)") : ""
|
|
342
347
|
const label = theme.fg("toolTitle", theme.bold("mine ")) + theme.fg("accent", source) + write
|
|
343
|
-
return { render: () => [label], invalidate() {} }
|
|
348
|
+
return { render: () => safeRender([label]), invalidate() {} }
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
export function mineTuplesRenderResult(result: any, _opts: { expanded: boolean }, theme: PiTheme): any {
|
|
@@ -350,7 +355,7 @@ export function mineTuplesRenderResult(result: any, _opts: { expanded: boolean }
|
|
|
350
355
|
if (/error|failed/i.test(line)) return theme.fg("error", line)
|
|
351
356
|
return theme.fg("toolOutput", line)
|
|
352
357
|
})
|
|
353
|
-
return { render: () => lines, invalidate() {} }
|
|
358
|
+
return { render: () => safeRender(lines), invalidate() {} }
|
|
354
359
|
}
|
|
355
360
|
|
|
356
361
|
// ─── Extract text helper ────────────────────────────────────────────────────
|