agentlas 1.0.25 → 1.0.26

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.26 — 2026-08-03
4
+
5
+ - **`agentlas list` stops letting the terminal cut its own output.** Slugs were
6
+ padded to a fixed width, so a longer one pushed that row's description out of
7
+ alignment, and the tagline had no bound at all — the terminal cut it mid-word
8
+ with no marker, so a short description and a truncated one looked identical.
9
+ Rows reached 109 display columns in an 80-column terminal. The slug column now
10
+ sizes to the widest slug present and the tagline is truncated on a word
11
+ boundary with an explicit ellipsis, measured in display cells so Korean and
12
+ other wide characters are counted at their real width. COLUMNS is honoured when
13
+ stdout is not a TTY.
14
+
3
15
  ## 1.0.25 — 2026-08-02
4
16
 
5
17
  - **Every Terminal session now closes a governed learning episode.** Direct
@@ -39,10 +39,63 @@ function run(ctx) {
39
39
  if (!agents.length) {
40
40
  ctx.out(" " + (en ? "(none yet — try: agentlas search \"what you need\")" : " (아직 없음 — agentlas search \"필요한 것\" 으로 찾아보세요)"));
41
41
  }
42
+ // padEnd alone does not align: a slug longer than the pad pushes that row's
43
+ // description out, and an unbounded tagline wraps and gets cut mid-word by the
44
+ // terminal with no marker, so the user cannot tell a short description from a
45
+ // truncated one. Clamp the slug column to the widest slug present (bounded),
46
+ // and truncate the tagline on a word boundary with an explicit ellipsis.
47
+ const SLUG_MAX = 32;
48
+ const slugWidth = Math.min(
49
+ SLUG_MAX,
50
+ agents.reduce((w, a) => Math.max(w, String(a.slug || "").length), 0) || 24,
51
+ );
52
+ const clampSlug = (slug) => {
53
+ const s = String(slug || "");
54
+ return s.length > slugWidth ? `${s.slice(0, slugWidth - 1)}…` : s.padEnd(slugWidth);
55
+ };
56
+ // CJK renders two columns wide while String.length counts one, so a Korean
57
+ // tagline measured by length overflows the terminal and gets cut by the
58
+ // terminal itself — exactly the unmarked mid-word break this fix removes.
59
+ const cellWidth = (ch) => {
60
+ const cp = ch.codePointAt(0);
61
+ return (cp >= 0x1100 && (
62
+ cp <= 0x115f
63
+ || (cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f)
64
+ || (cp >= 0xac00 && cp <= 0xd7a3)
65
+ || (cp >= 0xf900 && cp <= 0xfaff)
66
+ || (cp >= 0xfe30 && cp <= 0xfe6f)
67
+ || (cp >= 0xff00 && cp <= 0xff60)
68
+ || (cp >= 0xffe0 && cp <= 0xffe6)
69
+ || (cp >= 0x1f300 && cp <= 0x1f64f)
70
+ || (cp >= 0x20000 && cp <= 0x3fffd)
71
+ )) ? 2 : 1;
72
+ };
73
+ const displayWidth = (text) => [...String(text || "")].reduce((w, ch) => w + cellWidth(ch), 0);
74
+ const clampTag = (text, budget) => {
75
+ const t = String(text || "").replace(/\s+/g, " ").trim();
76
+ if (displayWidth(t) <= budget) return t;
77
+ let out = "";
78
+ let w = 0;
79
+ for (const ch of t) {
80
+ const next = w + cellWidth(ch);
81
+ if (next > budget - 1) break;
82
+ out += ch;
83
+ w = next;
84
+ }
85
+ const lastSpace = out.lastIndexOf(" ");
86
+ return `${(lastSpace > out.length * 0.6 ? out.slice(0, lastSpace) : out).trimEnd()}…`;
87
+ };
88
+ // Not a TTY (piped, redirected, CI) leaves process.stdout.columns undefined.
89
+ // Honour COLUMNS the way ordinary Unix tools do before falling back, so the
90
+ // output is reproducible and testable outside a terminal.
91
+ const cols = Number(process.stdout.columns) || Number(process.env.COLUMNS) || 100;
42
92
  for (const a of agents) {
43
93
  const name = en && a.name_en ? a.name_en : a.name;
44
94
  const tag = en && a.tagline_en ? a.tagline_en : a.tagline;
45
- ctx.out(` ${ctx.ui.accent(a.slug.padEnd(24))} ${name}${tag ? ctx.ui.dim(" — " + tag) : ""}`);
95
+ // 2 indent + slug + space + name + " — " + tagline must fit one line.
96
+ const budget = Math.max(20, cols - (2 + slugWidth + 1 + displayWidth(name) + 3));
97
+ const shown = tag ? clampTag(tag, budget) : "";
98
+ ctx.out(` ${ctx.ui.accent(clampSlug(a.slug))} ${name}${shown ? ctx.ui.dim(" — " + shown) : ""}`);
46
99
  }
47
100
  if (firms.length) {
48
101
  ctx.out("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"