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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfl",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Just Fucking Launch - CLI for AI-powered GTM and development",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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 ────────────────────────────────────────────────────