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